@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
|
|
|
@@ -114,9 +69,9 @@ describe('webex-core', () => {
|
|
|
114
69
|
|
|
115
70
|
it.each([
|
|
116
71
|
{error: new Error('failed'), expectedMessage: 'failed'},
|
|
117
|
-
{error: undefined, expectedMessage: undefined}
|
|
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}`
|
|
@@ -145,132 +98,329 @@ describe('webex-core', () => {
|
|
|
145
98
|
|
|
146
99
|
it.each([
|
|
147
100
|
{error: new Error('failed'), expectedMessage: 'failed'},
|
|
148
|
-
{error: undefined, expectedMessage: undefined}
|
|
149
|
-
])(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
},
|
|
159
|
-
};
|
|
101
|
+
{error: undefined, expectedMessage: undefined}
|
|
102
|
+
])('sets initFailed to true when initServiceCatalogs errors', async ({error, expectedMessage}) => {
|
|
103
|
+
services.initServiceCatalogs = sinon.stub().callsFake(() => {
|
|
104
|
+
return Promise.reject(error);
|
|
105
|
+
});
|
|
106
|
+
services.webex.credentials = {
|
|
107
|
+
supertoken: {
|
|
108
|
+
access_token: 'token'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
160
111
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
services.trigger = sinon.stub();
|
|
112
|
+
services.listenToOnce = sinon.stub();
|
|
113
|
+
services.logger.error = sinon.stub();
|
|
164
114
|
|
|
165
|
-
|
|
115
|
+
services.initialize();
|
|
166
116
|
|
|
167
|
-
|
|
168
|
-
|
|
117
|
+
// call the onReady callback
|
|
118
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
119
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
169
120
|
|
|
170
|
-
|
|
121
|
+
await waitForAsync();
|
|
171
122
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
);
|
|
123
|
+
assert.isTrue(services.initFailed);
|
|
124
|
+
sinon.assert.calledWith(
|
|
125
|
+
services.logger.error,
|
|
126
|
+
`services: failed to init initial services when credentials available, ${expectedMessage}`
|
|
127
|
+
);
|
|
128
|
+
});
|
|
181
129
|
|
|
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 = {};
|
|
130
|
+
it('sets services.ready to true when waitForCatalogInit is disabled (default)', () => {
|
|
131
|
+
services.listenToOnce = sinon.stub();
|
|
132
|
+
services.initialize();
|
|
191
133
|
|
|
192
|
-
|
|
134
|
+
// Fire the change:config handler which decides gated-vs-ungated and, in
|
|
135
|
+
// ungated mode, flips services.ready to true.
|
|
136
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
193
137
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
testCatalog.status.postauth.ready = false;
|
|
138
|
+
assert.isTrue(services.ready, 'services.ready should be true so it does not block webex.ready');
|
|
139
|
+
});
|
|
197
140
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
141
|
+
it('listens on the "ready" event when waitForCatalogInit is disabled (default)', () => {
|
|
142
|
+
services.listenToOnce = sinon.stub();
|
|
143
|
+
services.initialize();
|
|
201
144
|
|
|
202
|
-
|
|
203
|
-
|
|
145
|
+
// Fire change:config to trigger the mode-specific listener registration.
|
|
146
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
204
147
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
148
|
+
// Call 0 is 'change:config'; call 1 is the catalog init listener.
|
|
149
|
+
const [, event] = services.listenToOnce.getCall(1).args;
|
|
150
|
+
assert.equal(event, 'ready', 'default path listens on webex ready');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
208
153
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
154
|
+
describe('#initialize (waitForCatalogInit=true)', () => {
|
|
155
|
+
beforeEach(() => {
|
|
156
|
+
services.webex.config = {
|
|
157
|
+
...(services.webex.config || {}),
|
|
158
|
+
services: {waitForCatalogInit: true},
|
|
159
|
+
};
|
|
160
|
+
// Reset ready flag: the outer beforeEach constructs services which
|
|
161
|
+
// runs initialize() once with no config (ungated path, flips ready to
|
|
162
|
+
// true). We reset here so tests that call initialize() a second time
|
|
163
|
+
// with the flag enabled observe the gated behavior from a clean state.
|
|
164
|
+
services.ready = false;
|
|
165
|
+
});
|
|
212
166
|
|
|
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 = {};
|
|
167
|
+
it('leaves services.ready=false after initialize (does not flip until finalize)', () => {
|
|
168
|
+
services.listenToOnce = sinon.stub();
|
|
169
|
+
services.initialize();
|
|
221
170
|
|
|
222
|
-
|
|
171
|
+
assert.isFalse(services.ready, 'services.ready should block webex.ready until finalize');
|
|
172
|
+
});
|
|
223
173
|
|
|
224
|
-
|
|
225
|
-
|
|
174
|
+
it('listens on the "loaded" event (not "ready") to avoid deadlock', () => {
|
|
175
|
+
services.listenToOnce = sinon.stub();
|
|
176
|
+
services.initialize();
|
|
226
177
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
await waitForAsync();
|
|
178
|
+
// Fire change:config to trigger the mode-specific listener registration.
|
|
179
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
230
180
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
181
|
+
// Call 0 is 'change:config'; call 1 is the catalog init listener.
|
|
182
|
+
const [, event] = services.listenToOnce.getCall(1).args;
|
|
183
|
+
assert.equal(event, 'loaded', 'gated path must listen on loaded to avoid deadlock');
|
|
184
|
+
});
|
|
234
185
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
186
|
+
it('flips services.ready=true after init succeeds with credentials', async () => {
|
|
187
|
+
services.listenToOnce = sinon.stub();
|
|
188
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
189
|
+
services.webex.credentials = {
|
|
190
|
+
supertoken: {access_token: 'token'},
|
|
191
|
+
};
|
|
238
192
|
|
|
239
|
-
|
|
240
|
-
|
|
193
|
+
services.initialize();
|
|
194
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
195
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
196
|
+
await waitForAsync();
|
|
241
197
|
|
|
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 = {};
|
|
198
|
+
assert.isTrue(services.ready);
|
|
199
|
+
});
|
|
251
200
|
|
|
252
|
-
|
|
201
|
+
it('flips services.ready=true after init succeeds without credentials (preauth path)', async () => {
|
|
202
|
+
services.listenToOnce = sinon.stub();
|
|
203
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
253
204
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
205
|
+
services.initialize();
|
|
206
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
207
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
208
|
+
await waitForAsync();
|
|
257
209
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
await waitForAsync();
|
|
210
|
+
assert.isTrue(services.ready);
|
|
211
|
+
});
|
|
261
212
|
|
|
262
|
-
|
|
263
|
-
|
|
213
|
+
it('flips services.ready=true even when initServiceCatalogs errors, so webex.ready can fire', async () => {
|
|
214
|
+
services.listenToOnce = sinon.stub();
|
|
215
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.reject(new Error('boom')));
|
|
216
|
+
services.webex.credentials = {
|
|
217
|
+
supertoken: {access_token: 'token'},
|
|
218
|
+
};
|
|
219
|
+
services.logger.error = sinon.stub();
|
|
264
220
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
221
|
+
services.initialize();
|
|
222
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
223
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
224
|
+
await waitForAsync();
|
|
268
225
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
226
|
+
assert.isTrue(services.initFailed);
|
|
227
|
+
assert.isTrue(services.ready);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('flips services.ready=true even when collectPreauthCatalog errors', async () => {
|
|
231
|
+
services.listenToOnce = sinon.stub();
|
|
232
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.reject(new Error('boom')));
|
|
233
|
+
services.logger.error = sinon.stub();
|
|
234
|
+
|
|
235
|
+
services.initialize();
|
|
236
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
237
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
238
|
+
await waitForAsync();
|
|
239
|
+
|
|
240
|
+
assert.isTrue(services.initFailed);
|
|
241
|
+
assert.isTrue(services.ready);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('flips services.ready=true when init times out', async () => {
|
|
245
|
+
const clock = sinon.useFakeTimers();
|
|
246
|
+
services.listenToOnce = sinon.stub();
|
|
247
|
+
// Never resolves - the timeout must win the race.
|
|
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
|
+
// Advance past the 15s init timeout and flush microtasks so the
|
|
259
|
+
// rejected timeout can propagate through .catch and .finally into
|
|
260
|
+
// _finalizeReady().
|
|
261
|
+
await clock.tickAsync(15_001);
|
|
262
|
+
clock.restore();
|
|
263
|
+
|
|
264
|
+
assert.isTrue(services.initFailed);
|
|
265
|
+
assert.isTrue(services.ready);
|
|
266
|
+
sinon.assert.calledWithMatch(
|
|
267
|
+
services.logger.error,
|
|
268
|
+
/services: init timed out after 15000ms/
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('awaits an in-flight credentials refresh before flipping services.ready=true', async () => {
|
|
273
|
+
services.listenToOnce = sinon.stub();
|
|
274
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
275
|
+
|
|
276
|
+
let onChangeIsRefreshing;
|
|
277
|
+
services.webex.credentials = {
|
|
278
|
+
supertoken: {access_token: 'token'},
|
|
279
|
+
isRefreshing: true,
|
|
280
|
+
once: sinon.stub().callsFake((event, cb) => {
|
|
281
|
+
if (event === 'change:isRefreshing') onChangeIsRefreshing = cb;
|
|
282
|
+
}),
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
services.initialize();
|
|
286
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
287
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
288
|
+
await waitForAsync();
|
|
289
|
+
|
|
290
|
+
assert.isFalse(services.ready, 'must not flip ready while refresh is in flight');
|
|
291
|
+
assert.isFunction(onChangeIsRefreshing, 'must subscribe to change:isRefreshing');
|
|
292
|
+
sinon.assert.calledWith(services.webex.credentials.once, 'change:isRefreshing');
|
|
293
|
+
|
|
294
|
+
// Simulate refresh completing.
|
|
295
|
+
onChangeIsRefreshing();
|
|
296
|
+
await waitForAsync();
|
|
297
|
+
|
|
298
|
+
assert.isTrue(services.ready);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('registers a change:canAuthorize listener when there is no supertoken (fresh-login path)', async () => {
|
|
302
|
+
services.listenToOnce = sinon.stub();
|
|
303
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
304
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
305
|
+
|
|
306
|
+
services.initialize();
|
|
307
|
+
// initialize() creates a new catalog and replaces the WeakMap entry.
|
|
308
|
+
const currentCatalog = services._getCatalog();
|
|
309
|
+
// Fire the 'loaded' listener (index 1; index 0 was 'change:config').
|
|
310
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
311
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
312
|
+
await waitForAsync();
|
|
313
|
+
|
|
314
|
+
const freshLoginCall = services.listenToOnce
|
|
315
|
+
.getCalls()
|
|
316
|
+
.find((call) => call.args[1] === 'change:canAuthorize');
|
|
317
|
+
assert.isDefined(freshLoginCall, 'expected change:canAuthorize listener to be registered');
|
|
318
|
+
|
|
319
|
+
// Simulate OAuth completing.
|
|
320
|
+
services.webex.canAuthorize = true;
|
|
321
|
+
currentCatalog.status.postauth.ready = false;
|
|
322
|
+
freshLoginCall.args[2]();
|
|
323
|
+
|
|
324
|
+
assert.isTrue(
|
|
325
|
+
services.initServiceCatalogs.called,
|
|
326
|
+
'expected postauth catalog init after canAuthorize flips'
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('does not re-init postauth catalog when canAuthorize fires but catalog is already ready', async () => {
|
|
331
|
+
services.listenToOnce = sinon.stub();
|
|
332
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
333
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
334
|
+
|
|
335
|
+
services.initialize();
|
|
336
|
+
// initialize() creates a new catalog; grab the current one.
|
|
337
|
+
const currentCatalog = services._getCatalog();
|
|
338
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
339
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
340
|
+
await waitForAsync();
|
|
341
|
+
|
|
342
|
+
const freshLoginCall = services.listenToOnce
|
|
343
|
+
.getCalls()
|
|
344
|
+
.find((call) => call.args[1] === 'change:canAuthorize');
|
|
345
|
+
|
|
346
|
+
services.webex.canAuthorize = true;
|
|
347
|
+
currentCatalog.status.postauth.ready = true; // already collected
|
|
348
|
+
freshLoginCall.args[2]();
|
|
349
|
+
|
|
350
|
+
assert.isFalse(
|
|
351
|
+
services.initServiceCatalogs.called,
|
|
352
|
+
'must not re-fetch postauth catalog if it is already ready'
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('finalizes ready when cache is warm (skips network fetch)', async () => {
|
|
357
|
+
services.listenToOnce = sinon.stub();
|
|
358
|
+
services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(true));
|
|
359
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
360
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
361
|
+
|
|
362
|
+
services.initialize();
|
|
363
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
364
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
365
|
+
await waitForAsync();
|
|
366
|
+
await waitForAsync();
|
|
367
|
+
|
|
368
|
+
assert.isTrue(services.ready);
|
|
369
|
+
assert.isFalse(
|
|
370
|
+
services.initServiceCatalogs.called,
|
|
371
|
+
'must skip network fetch when cache is warm'
|
|
372
|
+
);
|
|
373
|
+
assert.isFalse(
|
|
374
|
+
services.collectPreauthCatalog.called,
|
|
375
|
+
'must skip preauth fetch when cache is warm'
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('#_finalizeReady', () => {
|
|
381
|
+
beforeEach(() => {
|
|
382
|
+
// Outer beforeEach construction already ran initialize() (ungated,
|
|
383
|
+
// sets ready=true). Reset so _finalizeReady's flip is observable.
|
|
384
|
+
services.ready = false;
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('sets ready=true immediately when credentials are not refreshing', async () => {
|
|
388
|
+
services.webex.credentials = {isRefreshing: false};
|
|
389
|
+
assert.isFalse(services.ready);
|
|
390
|
+
|
|
391
|
+
await services._finalizeReady();
|
|
392
|
+
|
|
393
|
+
assert.isTrue(services.ready);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('sets ready=true when credentials are missing', async () => {
|
|
397
|
+
services.webex.credentials = undefined;
|
|
398
|
+
|
|
399
|
+
await services._finalizeReady();
|
|
400
|
+
|
|
401
|
+
assert.isTrue(services.ready);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('awaits change:isRefreshing before setting ready=true', async () => {
|
|
405
|
+
let onChangeIsRefreshing;
|
|
406
|
+
services.webex.credentials = {
|
|
407
|
+
isRefreshing: true,
|
|
408
|
+
once: sinon.stub().callsFake((event, cb) => {
|
|
409
|
+
if (event === 'change:isRefreshing') onChangeIsRefreshing = cb;
|
|
410
|
+
}),
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const settled = services._finalizeReady();
|
|
414
|
+
// Let the async body reach its await point.
|
|
415
|
+
await waitForAsync();
|
|
416
|
+
|
|
417
|
+
assert.isFalse(services.ready, 'ready must not flip while refresh is in flight');
|
|
418
|
+
assert.isFunction(onChangeIsRefreshing);
|
|
419
|
+
|
|
420
|
+
onChangeIsRefreshing();
|
|
421
|
+
await settled;
|
|
422
|
+
|
|
423
|
+
assert.isTrue(services.ready);
|
|
274
424
|
});
|
|
275
425
|
});
|
|
276
426
|
|
|
@@ -309,7 +459,7 @@ describe('webex-core', () => {
|
|
|
309
459
|
|
|
310
460
|
services.collectPreauthCatalog = sinon.stub().callsFake(() => {
|
|
311
461
|
return Promise.resolve();
|
|
312
|
-
})
|
|
462
|
+
})
|
|
313
463
|
|
|
314
464
|
services.updateServices = sinon.stub().callsFake(() => {
|
|
315
465
|
return Promise.reject(error);
|
|
@@ -404,7 +554,7 @@ describe('webex-core', () => {
|
|
|
404
554
|
discovery: {
|
|
405
555
|
sqdiscovery: 'https://test.ciscospark.com/v1/region',
|
|
406
556
|
},
|
|
407
|
-
}
|
|
557
|
+
}
|
|
408
558
|
};
|
|
409
559
|
});
|
|
410
560
|
|
|
@@ -424,8 +574,8 @@ describe('webex-core', () => {
|
|
|
424
574
|
assert.calledWith(webex.request, {
|
|
425
575
|
uri: 'https://test.ciscospark.com/v1/region',
|
|
426
576
|
addAuthHeader: false,
|
|
427
|
-
headers: {'spark-user-agent': null},
|
|
428
|
-
timeout: 5000
|
|
577
|
+
headers: { 'spark-user-agent': null },
|
|
578
|
+
timeout: 5000
|
|
429
579
|
});
|
|
430
580
|
});
|
|
431
581
|
});
|
|
@@ -482,6 +632,7 @@ describe('webex-core', () => {
|
|
|
482
632
|
});
|
|
483
633
|
|
|
484
634
|
describe('#_fetchNewServiceHostmap()', () => {
|
|
635
|
+
|
|
485
636
|
beforeEach(() => {
|
|
486
637
|
sinon.spy(webex.internal.newMetrics.callDiagnosticLatencies, 'measureLatency');
|
|
487
638
|
});
|
|
@@ -495,20 +646,17 @@ describe('webex-core', () => {
|
|
|
495
646
|
|
|
496
647
|
sinon.stub(services, '_formatReceivedHostmap').resolves(mapResponse);
|
|
497
648
|
sinon.stub(services, 'request').resolves({});
|
|
498
|
-
|
|
649
|
+
|
|
499
650
|
const mapResult = await services._fetchNewServiceHostmap({from: 'limited'});
|
|
500
651
|
|
|
501
652
|
assert.calledOnceWithExactly(services.request, {
|
|
502
653
|
method: 'GET',
|
|
503
654
|
service: 'u2c',
|
|
504
655
|
resource: '/limited/catalog',
|
|
505
|
-
qs: {format: 'hostmap'}
|
|
506
|
-
}
|
|
507
|
-
assert.calledOnceWithExactly(
|
|
508
|
-
webex.internal.newMetrics.callDiagnosticLatencies.measureLatency,
|
|
509
|
-
sinon.match.func,
|
|
510
|
-
'internal.get.u2c.time'
|
|
656
|
+
qs: {format: 'hostmap'}
|
|
657
|
+
}
|
|
511
658
|
);
|
|
659
|
+
assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.u2c.time');
|
|
512
660
|
});
|
|
513
661
|
|
|
514
662
|
it('checks service request rejects', async () => {
|
|
@@ -516,7 +664,7 @@ describe('webex-core', () => {
|
|
|
516
664
|
|
|
517
665
|
sinon.spy(services, '_formatReceivedHostmap');
|
|
518
666
|
sinon.stub(services, 'request').rejects(error);
|
|
519
|
-
|
|
667
|
+
|
|
520
668
|
const promise = services._fetchNewServiceHostmap({from: 'limited'});
|
|
521
669
|
const rejectedValue = await assert.isRejected(promise);
|
|
522
670
|
|
|
@@ -528,13 +676,10 @@ describe('webex-core', () => {
|
|
|
528
676
|
method: 'GET',
|
|
529
677
|
service: 'u2c',
|
|
530
678
|
resource: '/limited/catalog',
|
|
531
|
-
qs: {format: 'hostmap'}
|
|
532
|
-
}
|
|
533
|
-
assert.calledOnceWithExactly(
|
|
534
|
-
webex.internal.newMetrics.callDiagnosticLatencies.measureLatency,
|
|
535
|
-
sinon.match.func,
|
|
536
|
-
'internal.get.u2c.time'
|
|
679
|
+
qs: {format: 'hostmap'}
|
|
680
|
+
}
|
|
537
681
|
);
|
|
682
|
+
assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.u2c.time');
|
|
538
683
|
});
|
|
539
684
|
});
|
|
540
685
|
|
|
@@ -565,6 +710,7 @@ describe('webex-core', () => {
|
|
|
565
710
|
});
|
|
566
711
|
|
|
567
712
|
it('returns the original uri if the hostmap has no hosts for the host', () => {
|
|
713
|
+
|
|
568
714
|
services._hostCatalog = {
|
|
569
715
|
'example.com': [],
|
|
570
716
|
};
|
|
@@ -728,7 +874,8 @@ describe('webex-core', () => {
|
|
|
728
874
|
id: '0:0:0:different-e-x',
|
|
729
875
|
},
|
|
730
876
|
],
|
|
731
|
-
'example-f.com': [
|
|
877
|
+
'example-f.com': [
|
|
878
|
+
],
|
|
732
879
|
},
|
|
733
880
|
format: 'hostmap',
|
|
734
881
|
};
|
|
@@ -936,7 +1083,7 @@ describe('webex-core', () => {
|
|
|
936
1083
|
defaultUrl: 'https://example-g.com/api/v1',
|
|
937
1084
|
hosts: [],
|
|
938
1085
|
name: 'example-g',
|
|
939
|
-
}
|
|
1086
|
+
}
|
|
940
1087
|
]);
|
|
941
1088
|
});
|
|
942
1089
|
|
|
@@ -990,59 +1137,34 @@ describe('webex-core', () => {
|
|
|
990
1137
|
assert.equal(webex.config.credentials.authorizeUrl, authUrl);
|
|
991
1138
|
});
|
|
992
1139
|
});
|
|
993
|
-
|
|
1140
|
+
|
|
994
1141
|
describe('#getMobiusClusters', () => {
|
|
995
1142
|
it('returns unique mobius host entries from hostCatalog', () => {
|
|
996
1143
|
// Arrange: two hostCatalog keys, with duplicate mobius host across keys
|
|
997
1144
|
services._hostCatalog = {
|
|
998
1145
|
'mobius-us-east-2.prod.infra.webex.com': [
|
|
999
|
-
{
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
priority: 5,
|
|
1003
|
-
id: 'urn:TEAM:xyz:mobius',
|
|
1004
|
-
},
|
|
1005
|
-
{
|
|
1006
|
-
host: 'mobius-eu-central-1.prod.infra.webex.com',
|
|
1007
|
-
ttl: -1,
|
|
1008
|
-
priority: 10,
|
|
1009
|
-
id: 'urn:TEAM:xyz:mobius',
|
|
1010
|
-
},
|
|
1011
|
-
],
|
|
1146
|
+
{host: 'mobius-us-east-2.prod.infra.webex.com', ttl: -1, priority: 5, id: 'urn:TEAM:xyz:mobius'},
|
|
1147
|
+
{host: 'mobius-eu-central-1.prod.infra.webex.com', ttl: -1, priority: 10, id: 'urn:TEAM:xyz:mobius'},
|
|
1148
|
+
],
|
|
1012
1149
|
|
|
1013
1150
|
'mobius-eu-central-1.prod.infra.webex.com': [
|
|
1014
|
-
{
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
priority: 7,
|
|
1018
|
-
id: 'urn:TEAM:xyz:mobius',
|
|
1019
|
-
}, // duplicate host
|
|
1020
|
-
],
|
|
1021
|
-
'wdm-a.webex.com': [
|
|
1151
|
+
{host: 'mobius-us-east-2.prod.infra.webex.com', ttl: -1, priority: 7, id: 'urn:TEAM:xyz:mobius'}, // duplicate host
|
|
1152
|
+
],
|
|
1153
|
+
'wdm-a.webex.com' : [
|
|
1022
1154
|
{host: 'wdm-a.webex.com', ttl: -1, priority: 5, id: 'urn:TEAM:xyz:wdm'},
|
|
1023
|
-
]
|
|
1155
|
+
]
|
|
1024
1156
|
};
|
|
1025
|
-
|
|
1157
|
+
|
|
1026
1158
|
// Act
|
|
1027
1159
|
const clusters = services.getMobiusClusters();
|
|
1028
|
-
|
|
1160
|
+
|
|
1029
1161
|
// Assert
|
|
1030
1162
|
// deduped; only mobius entries; keeps first seen mobius-a, then mobius-b
|
|
1031
1163
|
assert.deepEqual(
|
|
1032
1164
|
clusters.map(({host, id, ttl, priority}) => ({host, id, ttl, priority})),
|
|
1033
1165
|
[
|
|
1034
|
-
{
|
|
1035
|
-
|
|
1036
|
-
id: 'urn:TEAM:xyz:mobius',
|
|
1037
|
-
ttl: -1,
|
|
1038
|
-
priority: 5,
|
|
1039
|
-
},
|
|
1040
|
-
{
|
|
1041
|
-
host: 'mobius-eu-central-1.prod.infra.webex.com',
|
|
1042
|
-
id: 'urn:TEAM:xyz:mobius',
|
|
1043
|
-
ttl: -1,
|
|
1044
|
-
priority: 10,
|
|
1045
|
-
},
|
|
1166
|
+
{host: 'mobius-us-east-2.prod.infra.webex.com', id: 'urn:TEAM:xyz:mobius', ttl: -1, priority: 5},
|
|
1167
|
+
{host: 'mobius-eu-central-1.prod.infra.webex.com', id: 'urn:TEAM:xyz:mobius', ttl: -1, priority: 10},
|
|
1046
1168
|
]
|
|
1047
1169
|
);
|
|
1048
1170
|
});
|
|
@@ -1051,31 +1173,31 @@ describe('webex-core', () => {
|
|
|
1051
1173
|
describe('#isValidHost', () => {
|
|
1052
1174
|
beforeEach(() => {
|
|
1053
1175
|
// Setting up a mock host catalog
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1176
|
+
services._hostCatalog = {
|
|
1177
|
+
"audit-ci-m.wbx2.com": [
|
|
1178
|
+
{
|
|
1179
|
+
"host": "audit-ci-m.wbx2.com",
|
|
1180
|
+
"ttl": -1,
|
|
1181
|
+
"priority": 5,
|
|
1182
|
+
"id": "urn:IDENTITY:PA61:adminAudit"
|
|
1183
|
+
},
|
|
1184
|
+
{
|
|
1185
|
+
"host": "audit-ci-m.wbx2.com",
|
|
1186
|
+
"ttl": -1,
|
|
1187
|
+
"priority": 5,
|
|
1188
|
+
"id": "urn:IDENTITY:PA61:adminAuditV2"
|
|
1189
|
+
}
|
|
1190
|
+
],
|
|
1191
|
+
"mercury-connection-partition0-r.wbx2.com": [
|
|
1192
|
+
{
|
|
1193
|
+
"host": "mercury-connection-partition0-r.wbx2.com",
|
|
1194
|
+
"ttl": -1,
|
|
1195
|
+
"priority": 5,
|
|
1196
|
+
"id": "urn:TEAM:us-west-2_r:mercuryConnectionPartition0"
|
|
1197
|
+
}
|
|
1198
|
+
],
|
|
1199
|
+
"empty.com": []
|
|
1200
|
+
};
|
|
1079
1201
|
});
|
|
1080
1202
|
afterAll(() => {
|
|
1081
1203
|
// Clean up the mock host catalog
|
|
@@ -1157,7 +1279,7 @@ describe('webex-core', () => {
|
|
|
1157
1279
|
let catalog;
|
|
1158
1280
|
let localStorageBackup;
|
|
1159
1281
|
let windowBackup;
|
|
1160
|
-
|
|
1282
|
+
|
|
1161
1283
|
const makeLocalStorageShim = () => {
|
|
1162
1284
|
const store = new Map();
|
|
1163
1285
|
return {
|
|
@@ -1167,16 +1289,13 @@ describe('webex-core', () => {
|
|
|
1167
1289
|
_store: store,
|
|
1168
1290
|
};
|
|
1169
1291
|
};
|
|
1170
|
-
|
|
1292
|
+
|
|
1171
1293
|
beforeEach(() => {
|
|
1172
1294
|
// Build a fresh webex instance
|
|
1173
|
-
webex = new MockWebex({
|
|
1174
|
-
children: {services: Services},
|
|
1175
|
-
config: {credentials: {federation: true}},
|
|
1176
|
-
});
|
|
1295
|
+
webex = new MockWebex({children: {services: Services}, config: {credentials: {federation: true}}});
|
|
1177
1296
|
services = webex.internal.services;
|
|
1178
1297
|
catalog = services._getCatalog();
|
|
1179
|
-
|
|
1298
|
+
|
|
1180
1299
|
// enable U2C caching feature flag in tests that rely on localStorage writes/reads
|
|
1181
1300
|
services.webex.config = services.webex.config || {};
|
|
1182
1301
|
services.webex.config.calling = {...(services.webex.config.calling || {}), cacheU2C: true};
|
|
@@ -1188,15 +1307,13 @@ describe('webex-core', () => {
|
|
|
1188
1307
|
global.window.localStorage = makeLocalStorageShim();
|
|
1189
1308
|
// Ensure code under test uses our shim via util method
|
|
1190
1309
|
sinon.stub(services, '_getLocalStorageSafe').returns(global.window.localStorage);
|
|
1191
|
-
|
|
1310
|
+
|
|
1192
1311
|
// Stub the formatter so we don't need a full hostmap payload in tests
|
|
1193
|
-
sinon
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1197
|
-
]);
|
|
1312
|
+
sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
|
|
1313
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1314
|
+
]);
|
|
1198
1315
|
});
|
|
1199
|
-
|
|
1316
|
+
|
|
1200
1317
|
afterEach(() => {
|
|
1201
1318
|
global.window.localStorage = localStorageBackup || undefined;
|
|
1202
1319
|
if (!windowBackup) {
|
|
@@ -1209,7 +1326,7 @@ describe('webex-core', () => {
|
|
|
1209
1326
|
services._getLocalStorageSafe.restore();
|
|
1210
1327
|
}
|
|
1211
1328
|
});
|
|
1212
|
-
|
|
1329
|
+
|
|
1213
1330
|
it('invokes initServiceCatalogs on ready, caches catalog, and stores in localStorage', async () => {
|
|
1214
1331
|
// Arrange: authenticated credentials and spies
|
|
1215
1332
|
services.webex.credentials = {
|
|
@@ -1221,20 +1338,10 @@ describe('webex-core', () => {
|
|
|
1221
1338
|
const cacheSpy = sinon.spy(services, '_cacheCatalog');
|
|
1222
1339
|
const setItemSpy = sinon.spy(global.window.localStorage, 'setItem');
|
|
1223
1340
|
// Make fetch return a hostmap object and allow formatter to reduce it
|
|
1224
|
-
sinon
|
|
1225
|
-
|
|
1226
|
-
.resolves({
|
|
1227
|
-
body: {
|
|
1228
|
-
services: [],
|
|
1229
|
-
activeServices: {},
|
|
1230
|
-
timestamp: Date.now().toString(),
|
|
1231
|
-
orgId: 'urn:EXAMPLE:org',
|
|
1232
|
-
format: 'U2CV2',
|
|
1233
|
-
},
|
|
1234
|
-
});
|
|
1235
|
-
// Cause loaded callback to run immediately
|
|
1341
|
+
sinon.stub(services, 'request').resolves({body: {services: [], activeServices: {}, timestamp: Date.now().toString(), orgId: 'urn:EXAMPLE:org', format: 'U2CV2'}});
|
|
1342
|
+
// Cause ready callback to run immediately
|
|
1236
1343
|
services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
|
|
1237
|
-
if (event === '
|
|
1344
|
+
if (event === 'change:config' || event === 'ready') cb();
|
|
1238
1345
|
});
|
|
1239
1346
|
|
|
1240
1347
|
// Act
|
|
@@ -1274,9 +1381,9 @@ describe('webex-core', () => {
|
|
|
1274
1381
|
|
|
1275
1382
|
const initSpy = sinon.spy(services, 'initServiceCatalogs');
|
|
1276
1383
|
const cacheSpy = sinon.spy(services, '_cacheCatalog');
|
|
1277
|
-
// Cause
|
|
1384
|
+
// Cause ready callback to run immediately
|
|
1278
1385
|
services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
|
|
1279
|
-
if (event === '
|
|
1386
|
+
if (event === 'change:config' || event === 'ready') cb();
|
|
1280
1387
|
});
|
|
1281
1388
|
|
|
1282
1389
|
// Act
|
|
@@ -1284,18 +1391,9 @@ describe('webex-core', () => {
|
|
|
1284
1391
|
await waitForAsync();
|
|
1285
1392
|
|
|
1286
1393
|
// Assert: ready path found cache and skipped initServiceCatalogs
|
|
1287
|
-
assert.isFalse(
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
);
|
|
1291
|
-
assert.isTrue(
|
|
1292
|
-
services._getCatalog().status.preauth.ready,
|
|
1293
|
-
'preauth should be ready from cache'
|
|
1294
|
-
);
|
|
1295
|
-
assert.isTrue(
|
|
1296
|
-
services._getCatalog().status.postauth.ready,
|
|
1297
|
-
'postauth should be ready from cache'
|
|
1298
|
-
);
|
|
1394
|
+
assert.isFalse(initSpy.called, 'expected initServiceCatalogs to be skipped with cache present');
|
|
1395
|
+
assert.isTrue(services._getCatalog().status.preauth.ready, 'preauth should be ready from cache');
|
|
1396
|
+
assert.isTrue(services._getCatalog().status.postauth.ready, 'postauth should be ready from cache');
|
|
1299
1397
|
assert.isFalse(cacheSpy.called, 'should not write cache during warm-up-only path');
|
|
1300
1398
|
|
|
1301
1399
|
// Cleanup
|
|
@@ -1311,29 +1409,26 @@ describe('webex-core', () => {
|
|
|
1311
1409
|
preauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1312
1410
|
postauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1313
1411
|
};
|
|
1314
|
-
|
|
1412
|
+
|
|
1315
1413
|
window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify(staleCached));
|
|
1316
|
-
|
|
1414
|
+
|
|
1317
1415
|
const warmed = await services._loadCatalogFromCache();
|
|
1318
|
-
|
|
1416
|
+
|
|
1319
1417
|
assert.isFalse(warmed, 'stale cache must not warm');
|
|
1320
|
-
assert.isNull(
|
|
1321
|
-
window.localStorage.getItem(CATALOG_CACHE_KEY_V1),
|
|
1322
|
-
'expired cache must be cleared'
|
|
1323
|
-
);
|
|
1418
|
+
assert.isNull(window.localStorage.getItem(CATALOG_CACHE_KEY_V1), 'expired cache must be cleared');
|
|
1324
1419
|
assert.isFalse(catalog.status.preauth.ready);
|
|
1325
1420
|
assert.isFalse(catalog.status.postauth.ready);
|
|
1326
1421
|
});
|
|
1327
|
-
|
|
1422
|
+
|
|
1328
1423
|
it('clearCatalogCache() removes the cached entry', async () => {
|
|
1329
1424
|
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1330
1425
|
window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify({cachedAt: Date.now()}));
|
|
1331
|
-
|
|
1426
|
+
|
|
1332
1427
|
await services.clearCatalogCache();
|
|
1333
|
-
|
|
1428
|
+
|
|
1334
1429
|
assert.isNull(window.localStorage.getItem(CATALOG_CACHE_KEY_V1), 'cache should be cleared');
|
|
1335
1430
|
});
|
|
1336
|
-
|
|
1431
|
+
|
|
1337
1432
|
it('still fetches when forceRefresh=true even if ready', async () => {
|
|
1338
1433
|
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1339
1434
|
window.localStorage.setItem(
|
|
@@ -1345,24 +1440,20 @@ describe('webex-core', () => {
|
|
|
1345
1440
|
postauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1346
1441
|
})
|
|
1347
1442
|
);
|
|
1348
|
-
|
|
1443
|
+
|
|
1349
1444
|
// warm from cache
|
|
1350
1445
|
const warmed = await services._loadCatalogFromCache();
|
|
1351
1446
|
assert.isTrue(warmed);
|
|
1352
1447
|
assert.isTrue(catalog.status.preauth.ready);
|
|
1353
1448
|
assert.isTrue(catalog.status.postauth.ready);
|
|
1354
|
-
|
|
1449
|
+
|
|
1355
1450
|
const fetchSpy = sinon.spy(services, '_fetchNewServiceHostmap');
|
|
1356
|
-
|
|
1451
|
+
|
|
1357
1452
|
// with forceRefresh we should fetch despite ready=true
|
|
1358
|
-
await services.updateServices({
|
|
1359
|
-
from: 'limited',
|
|
1360
|
-
query: {orgId: 'urn:EXAMPLE:org'},
|
|
1361
|
-
forceRefresh: true,
|
|
1362
|
-
});
|
|
1453
|
+
await services.updateServices({from: 'limited', query: {orgId: 'urn:EXAMPLE:org'}, forceRefresh: true});
|
|
1363
1454
|
// pass an empty query to avoid spreading undefined in qs construction
|
|
1364
1455
|
await services.updateServices({forceRefresh: true});
|
|
1365
|
-
|
|
1456
|
+
|
|
1366
1457
|
assert.isTrue(fetchSpy.called, 'forceRefresh should bypass cache short-circuit');
|
|
1367
1458
|
fetchSpy.restore();
|
|
1368
1459
|
});
|
|
@@ -1423,11 +1514,9 @@ describe('webex-core', () => {
|
|
|
1423
1514
|
);
|
|
1424
1515
|
// formatter returns at least one entry to mark ready
|
|
1425
1516
|
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1426
|
-
sinon
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1430
|
-
]);
|
|
1517
|
+
sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
|
|
1518
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1519
|
+
]);
|
|
1431
1520
|
|
|
1432
1521
|
const warmed = await services._loadCatalogFromCache();
|
|
1433
1522
|
assert.isTrue(warmed);
|
|
@@ -1449,11 +1538,9 @@ describe('webex-core', () => {
|
|
|
1449
1538
|
})
|
|
1450
1539
|
);
|
|
1451
1540
|
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1452
|
-
sinon
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1456
|
-
]);
|
|
1541
|
+
sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
|
|
1542
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1543
|
+
]);
|
|
1457
1544
|
|
|
1458
1545
|
const warmed = await services._loadCatalogFromCache();
|
|
1459
1546
|
// function returns true if overall cache path succeeded; we only verify group readiness
|
|
@@ -1480,17 +1567,12 @@ describe('webex-core', () => {
|
|
|
1480
1567
|
})
|
|
1481
1568
|
);
|
|
1482
1569
|
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1483
|
-
sinon
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1487
|
-
]);
|
|
1570
|
+
sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
|
|
1571
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1572
|
+
]);
|
|
1488
1573
|
|
|
1489
1574
|
await services._loadCatalogFromCache();
|
|
1490
|
-
assert.isFalse(
|
|
1491
|
-
catalog.status.preauth.ready,
|
|
1492
|
-
'preauth should not warm on selection mismatch'
|
|
1493
|
-
);
|
|
1575
|
+
assert.isFalse(catalog.status.preauth.ready, 'preauth should not warm on selection mismatch');
|
|
1494
1576
|
});
|
|
1495
1577
|
|
|
1496
1578
|
it('skips warming when environment fingerprint mismatches', async () => {
|
|
@@ -1501,10 +1583,7 @@ describe('webex-core', () => {
|
|
|
1501
1583
|
JSON.stringify({
|
|
1502
1584
|
cachedAt: Date.now(),
|
|
1503
1585
|
env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.other.com/u2c/api/v1'},
|
|
1504
|
-
preauth: {
|
|
1505
|
-
hostMap: {serviceLinks: {}, hostCatalog: {}},
|
|
1506
|
-
meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'},
|
|
1507
|
-
},
|
|
1586
|
+
preauth: {hostMap: {serviceLinks: {}, hostCatalog: {}}, meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'}},
|
|
1508
1587
|
})
|
|
1509
1588
|
);
|
|
1510
1589
|
// current env
|
|
@@ -1519,6 +1598,7 @@ describe('webex-core', () => {
|
|
|
1519
1598
|
assert.isFalse(catalog.status.postauth.ready);
|
|
1520
1599
|
});
|
|
1521
1600
|
});
|
|
1601
|
+
|
|
1522
1602
|
});
|
|
1523
1603
|
});
|
|
1524
1604
|
/* eslint-enable no-underscore-dangle */
|