@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
|
|
|
@@ -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,338 @@ 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
|
+
// After the postauth catalog init resolves, catalog.isReady must flip
|
|
330
|
+
// to true so downstream consumers see the catalog as usable.
|
|
331
|
+
await waitForAsync();
|
|
332
|
+
|
|
333
|
+
assert.isTrue(
|
|
334
|
+
currentCatalog.isReady,
|
|
335
|
+
'expected catalog.isReady=true after postauth catalog init succeeds'
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('does not re-init postauth catalog when canAuthorize fires but catalog is already ready', async () => {
|
|
340
|
+
services.listenToOnce = sinon.stub();
|
|
341
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
342
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
343
|
+
|
|
344
|
+
services.initialize();
|
|
345
|
+
// initialize() creates a new catalog; grab the current one.
|
|
346
|
+
const currentCatalog = services._getCatalog();
|
|
347
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
348
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
349
|
+
await waitForAsync();
|
|
350
|
+
|
|
351
|
+
const freshLoginCall = services.listenToOnce
|
|
352
|
+
.getCalls()
|
|
353
|
+
.find((call) => call.args[1] === 'change:canAuthorize');
|
|
354
|
+
|
|
355
|
+
services.webex.canAuthorize = true;
|
|
356
|
+
currentCatalog.status.postauth.ready = true; // already collected
|
|
357
|
+
freshLoginCall.args[2]();
|
|
358
|
+
|
|
359
|
+
assert.isFalse(
|
|
360
|
+
services.initServiceCatalogs.called,
|
|
361
|
+
'must not re-fetch postauth catalog if it is already ready'
|
|
362
|
+
);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('finalizes ready when cache is warm (skips network fetch)', async () => {
|
|
366
|
+
services.listenToOnce = sinon.stub();
|
|
367
|
+
services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(true));
|
|
368
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
369
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
370
|
+
|
|
371
|
+
services.initialize();
|
|
372
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
373
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
374
|
+
await waitForAsync();
|
|
375
|
+
await waitForAsync();
|
|
376
|
+
|
|
377
|
+
assert.isTrue(services.ready);
|
|
378
|
+
assert.isFalse(
|
|
379
|
+
services.initServiceCatalogs.called,
|
|
380
|
+
'must skip network fetch when cache is warm'
|
|
381
|
+
);
|
|
382
|
+
assert.isFalse(
|
|
383
|
+
services.collectPreauthCatalog.called,
|
|
384
|
+
'must skip preauth fetch when cache is warm'
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe('#_finalizeReady', () => {
|
|
390
|
+
beforeEach(() => {
|
|
391
|
+
// Outer beforeEach construction already ran initialize() (ungated,
|
|
392
|
+
// sets ready=true). Reset so _finalizeReady's flip is observable.
|
|
393
|
+
services.ready = false;
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('sets ready=true immediately when credentials are not refreshing', async () => {
|
|
397
|
+
services.webex.credentials = {isRefreshing: false};
|
|
398
|
+
assert.isFalse(services.ready);
|
|
399
|
+
|
|
400
|
+
await services._finalizeReady();
|
|
401
|
+
|
|
402
|
+
assert.isTrue(services.ready);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('sets ready=true when credentials are missing', async () => {
|
|
406
|
+
services.webex.credentials = undefined;
|
|
407
|
+
|
|
408
|
+
await services._finalizeReady();
|
|
409
|
+
|
|
410
|
+
assert.isTrue(services.ready);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('awaits change:isRefreshing before setting ready=true', async () => {
|
|
414
|
+
let onChangeIsRefreshing;
|
|
415
|
+
services.webex.credentials = {
|
|
416
|
+
isRefreshing: true,
|
|
417
|
+
once: sinon.stub().callsFake((event, cb) => {
|
|
418
|
+
if (event === 'change:isRefreshing') onChangeIsRefreshing = cb;
|
|
419
|
+
}),
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const settled = services._finalizeReady();
|
|
423
|
+
// Let the async body reach its await point.
|
|
424
|
+
await waitForAsync();
|
|
425
|
+
|
|
426
|
+
assert.isFalse(services.ready, 'ready must not flip while refresh is in flight');
|
|
427
|
+
assert.isFunction(onChangeIsRefreshing);
|
|
428
|
+
|
|
429
|
+
onChangeIsRefreshing();
|
|
430
|
+
await settled;
|
|
431
|
+
|
|
432
|
+
assert.isTrue(services.ready);
|
|
274
433
|
});
|
|
275
434
|
});
|
|
276
435
|
|
|
@@ -309,7 +468,7 @@ describe('webex-core', () => {
|
|
|
309
468
|
|
|
310
469
|
services.collectPreauthCatalog = sinon.stub().callsFake(() => {
|
|
311
470
|
return Promise.resolve();
|
|
312
|
-
})
|
|
471
|
+
})
|
|
313
472
|
|
|
314
473
|
services.updateServices = sinon.stub().callsFake(() => {
|
|
315
474
|
return Promise.reject(error);
|
|
@@ -404,7 +563,7 @@ describe('webex-core', () => {
|
|
|
404
563
|
discovery: {
|
|
405
564
|
sqdiscovery: 'https://test.ciscospark.com/v1/region',
|
|
406
565
|
},
|
|
407
|
-
}
|
|
566
|
+
}
|
|
408
567
|
};
|
|
409
568
|
});
|
|
410
569
|
|
|
@@ -424,8 +583,8 @@ describe('webex-core', () => {
|
|
|
424
583
|
assert.calledWith(webex.request, {
|
|
425
584
|
uri: 'https://test.ciscospark.com/v1/region',
|
|
426
585
|
addAuthHeader: false,
|
|
427
|
-
headers: {'spark-user-agent': null},
|
|
428
|
-
timeout: 5000
|
|
586
|
+
headers: { 'spark-user-agent': null },
|
|
587
|
+
timeout: 5000
|
|
429
588
|
});
|
|
430
589
|
});
|
|
431
590
|
});
|
|
@@ -482,6 +641,7 @@ describe('webex-core', () => {
|
|
|
482
641
|
});
|
|
483
642
|
|
|
484
643
|
describe('#_fetchNewServiceHostmap()', () => {
|
|
644
|
+
|
|
485
645
|
beforeEach(() => {
|
|
486
646
|
sinon.spy(webex.internal.newMetrics.callDiagnosticLatencies, 'measureLatency');
|
|
487
647
|
});
|
|
@@ -495,20 +655,17 @@ describe('webex-core', () => {
|
|
|
495
655
|
|
|
496
656
|
sinon.stub(services, '_formatReceivedHostmap').resolves(mapResponse);
|
|
497
657
|
sinon.stub(services, 'request').resolves({});
|
|
498
|
-
|
|
658
|
+
|
|
499
659
|
const mapResult = await services._fetchNewServiceHostmap({from: 'limited'});
|
|
500
660
|
|
|
501
661
|
assert.calledOnceWithExactly(services.request, {
|
|
502
662
|
method: 'GET',
|
|
503
663
|
service: 'u2c',
|
|
504
664
|
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'
|
|
665
|
+
qs: {format: 'hostmap'}
|
|
666
|
+
}
|
|
511
667
|
);
|
|
668
|
+
assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.u2c.time');
|
|
512
669
|
});
|
|
513
670
|
|
|
514
671
|
it('checks service request rejects', async () => {
|
|
@@ -516,7 +673,7 @@ describe('webex-core', () => {
|
|
|
516
673
|
|
|
517
674
|
sinon.spy(services, '_formatReceivedHostmap');
|
|
518
675
|
sinon.stub(services, 'request').rejects(error);
|
|
519
|
-
|
|
676
|
+
|
|
520
677
|
const promise = services._fetchNewServiceHostmap({from: 'limited'});
|
|
521
678
|
const rejectedValue = await assert.isRejected(promise);
|
|
522
679
|
|
|
@@ -528,13 +685,10 @@ describe('webex-core', () => {
|
|
|
528
685
|
method: 'GET',
|
|
529
686
|
service: 'u2c',
|
|
530
687
|
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'
|
|
688
|
+
qs: {format: 'hostmap'}
|
|
689
|
+
}
|
|
537
690
|
);
|
|
691
|
+
assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.u2c.time');
|
|
538
692
|
});
|
|
539
693
|
});
|
|
540
694
|
|
|
@@ -565,6 +719,7 @@ describe('webex-core', () => {
|
|
|
565
719
|
});
|
|
566
720
|
|
|
567
721
|
it('returns the original uri if the hostmap has no hosts for the host', () => {
|
|
722
|
+
|
|
568
723
|
services._hostCatalog = {
|
|
569
724
|
'example.com': [],
|
|
570
725
|
};
|
|
@@ -728,7 +883,8 @@ describe('webex-core', () => {
|
|
|
728
883
|
id: '0:0:0:different-e-x',
|
|
729
884
|
},
|
|
730
885
|
],
|
|
731
|
-
'example-f.com': [
|
|
886
|
+
'example-f.com': [
|
|
887
|
+
],
|
|
732
888
|
},
|
|
733
889
|
format: 'hostmap',
|
|
734
890
|
};
|
|
@@ -936,7 +1092,7 @@ describe('webex-core', () => {
|
|
|
936
1092
|
defaultUrl: 'https://example-g.com/api/v1',
|
|
937
1093
|
hosts: [],
|
|
938
1094
|
name: 'example-g',
|
|
939
|
-
}
|
|
1095
|
+
}
|
|
940
1096
|
]);
|
|
941
1097
|
});
|
|
942
1098
|
|
|
@@ -990,59 +1146,34 @@ describe('webex-core', () => {
|
|
|
990
1146
|
assert.equal(webex.config.credentials.authorizeUrl, authUrl);
|
|
991
1147
|
});
|
|
992
1148
|
});
|
|
993
|
-
|
|
1149
|
+
|
|
994
1150
|
describe('#getMobiusClusters', () => {
|
|
995
1151
|
it('returns unique mobius host entries from hostCatalog', () => {
|
|
996
1152
|
// Arrange: two hostCatalog keys, with duplicate mobius host across keys
|
|
997
1153
|
services._hostCatalog = {
|
|
998
1154
|
'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
|
-
],
|
|
1155
|
+
{host: 'mobius-us-east-2.prod.infra.webex.com', ttl: -1, priority: 5, id: 'urn:TEAM:xyz:mobius'},
|
|
1156
|
+
{host: 'mobius-eu-central-1.prod.infra.webex.com', ttl: -1, priority: 10, id: 'urn:TEAM:xyz:mobius'},
|
|
1157
|
+
],
|
|
1012
1158
|
|
|
1013
1159
|
'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': [
|
|
1160
|
+
{host: 'mobius-us-east-2.prod.infra.webex.com', ttl: -1, priority: 7, id: 'urn:TEAM:xyz:mobius'}, // duplicate host
|
|
1161
|
+
],
|
|
1162
|
+
'wdm-a.webex.com' : [
|
|
1022
1163
|
{host: 'wdm-a.webex.com', ttl: -1, priority: 5, id: 'urn:TEAM:xyz:wdm'},
|
|
1023
|
-
]
|
|
1164
|
+
]
|
|
1024
1165
|
};
|
|
1025
|
-
|
|
1166
|
+
|
|
1026
1167
|
// Act
|
|
1027
1168
|
const clusters = services.getMobiusClusters();
|
|
1028
|
-
|
|
1169
|
+
|
|
1029
1170
|
// Assert
|
|
1030
1171
|
// deduped; only mobius entries; keeps first seen mobius-a, then mobius-b
|
|
1031
1172
|
assert.deepEqual(
|
|
1032
1173
|
clusters.map(({host, id, ttl, priority}) => ({host, id, ttl, priority})),
|
|
1033
1174
|
[
|
|
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
|
-
},
|
|
1175
|
+
{host: 'mobius-us-east-2.prod.infra.webex.com', id: 'urn:TEAM:xyz:mobius', ttl: -1, priority: 5},
|
|
1176
|
+
{host: 'mobius-eu-central-1.prod.infra.webex.com', id: 'urn:TEAM:xyz:mobius', ttl: -1, priority: 10},
|
|
1046
1177
|
]
|
|
1047
1178
|
);
|
|
1048
1179
|
});
|
|
@@ -1051,31 +1182,31 @@ describe('webex-core', () => {
|
|
|
1051
1182
|
describe('#isValidHost', () => {
|
|
1052
1183
|
beforeEach(() => {
|
|
1053
1184
|
// 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
|
-
|
|
1185
|
+
services._hostCatalog = {
|
|
1186
|
+
"audit-ci-m.wbx2.com": [
|
|
1187
|
+
{
|
|
1188
|
+
"host": "audit-ci-m.wbx2.com",
|
|
1189
|
+
"ttl": -1,
|
|
1190
|
+
"priority": 5,
|
|
1191
|
+
"id": "urn:IDENTITY:PA61:adminAudit"
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
"host": "audit-ci-m.wbx2.com",
|
|
1195
|
+
"ttl": -1,
|
|
1196
|
+
"priority": 5,
|
|
1197
|
+
"id": "urn:IDENTITY:PA61:adminAuditV2"
|
|
1198
|
+
}
|
|
1199
|
+
],
|
|
1200
|
+
"mercury-connection-partition0-r.wbx2.com": [
|
|
1201
|
+
{
|
|
1202
|
+
"host": "mercury-connection-partition0-r.wbx2.com",
|
|
1203
|
+
"ttl": -1,
|
|
1204
|
+
"priority": 5,
|
|
1205
|
+
"id": "urn:TEAM:us-west-2_r:mercuryConnectionPartition0"
|
|
1206
|
+
}
|
|
1207
|
+
],
|
|
1208
|
+
"empty.com": []
|
|
1209
|
+
};
|
|
1079
1210
|
});
|
|
1080
1211
|
afterAll(() => {
|
|
1081
1212
|
// Clean up the mock host catalog
|
|
@@ -1157,7 +1288,7 @@ describe('webex-core', () => {
|
|
|
1157
1288
|
let catalog;
|
|
1158
1289
|
let localStorageBackup;
|
|
1159
1290
|
let windowBackup;
|
|
1160
|
-
|
|
1291
|
+
|
|
1161
1292
|
const makeLocalStorageShim = () => {
|
|
1162
1293
|
const store = new Map();
|
|
1163
1294
|
return {
|
|
@@ -1167,16 +1298,13 @@ describe('webex-core', () => {
|
|
|
1167
1298
|
_store: store,
|
|
1168
1299
|
};
|
|
1169
1300
|
};
|
|
1170
|
-
|
|
1301
|
+
|
|
1171
1302
|
beforeEach(() => {
|
|
1172
1303
|
// Build a fresh webex instance
|
|
1173
|
-
webex = new MockWebex({
|
|
1174
|
-
children: {services: Services},
|
|
1175
|
-
config: {credentials: {federation: true}},
|
|
1176
|
-
});
|
|
1304
|
+
webex = new MockWebex({children: {services: Services}, config: {credentials: {federation: true}}});
|
|
1177
1305
|
services = webex.internal.services;
|
|
1178
1306
|
catalog = services._getCatalog();
|
|
1179
|
-
|
|
1307
|
+
|
|
1180
1308
|
// enable U2C caching feature flag in tests that rely on localStorage writes/reads
|
|
1181
1309
|
services.webex.config = services.webex.config || {};
|
|
1182
1310
|
services.webex.config.calling = {...(services.webex.config.calling || {}), cacheU2C: true};
|
|
@@ -1188,15 +1316,13 @@ describe('webex-core', () => {
|
|
|
1188
1316
|
global.window.localStorage = makeLocalStorageShim();
|
|
1189
1317
|
// Ensure code under test uses our shim via util method
|
|
1190
1318
|
sinon.stub(services, '_getLocalStorageSafe').returns(global.window.localStorage);
|
|
1191
|
-
|
|
1319
|
+
|
|
1192
1320
|
// 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
|
-
]);
|
|
1321
|
+
sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
|
|
1322
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1323
|
+
]);
|
|
1198
1324
|
});
|
|
1199
|
-
|
|
1325
|
+
|
|
1200
1326
|
afterEach(() => {
|
|
1201
1327
|
global.window.localStorage = localStorageBackup || undefined;
|
|
1202
1328
|
if (!windowBackup) {
|
|
@@ -1209,7 +1335,7 @@ describe('webex-core', () => {
|
|
|
1209
1335
|
services._getLocalStorageSafe.restore();
|
|
1210
1336
|
}
|
|
1211
1337
|
});
|
|
1212
|
-
|
|
1338
|
+
|
|
1213
1339
|
it('invokes initServiceCatalogs on ready, caches catalog, and stores in localStorage', async () => {
|
|
1214
1340
|
// Arrange: authenticated credentials and spies
|
|
1215
1341
|
services.webex.credentials = {
|
|
@@ -1221,20 +1347,10 @@ describe('webex-core', () => {
|
|
|
1221
1347
|
const cacheSpy = sinon.spy(services, '_cacheCatalog');
|
|
1222
1348
|
const setItemSpy = sinon.spy(global.window.localStorage, 'setItem');
|
|
1223
1349
|
// 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
|
|
1350
|
+
sinon.stub(services, 'request').resolves({body: {services: [], activeServices: {}, timestamp: Date.now().toString(), orgId: 'urn:EXAMPLE:org', format: 'U2CV2'}});
|
|
1351
|
+
// Cause ready callback to run immediately
|
|
1236
1352
|
services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
|
|
1237
|
-
if (event === '
|
|
1353
|
+
if (event === 'change:config' || event === 'ready') cb();
|
|
1238
1354
|
});
|
|
1239
1355
|
|
|
1240
1356
|
// Act
|
|
@@ -1274,9 +1390,9 @@ describe('webex-core', () => {
|
|
|
1274
1390
|
|
|
1275
1391
|
const initSpy = sinon.spy(services, 'initServiceCatalogs');
|
|
1276
1392
|
const cacheSpy = sinon.spy(services, '_cacheCatalog');
|
|
1277
|
-
// Cause
|
|
1393
|
+
// Cause ready callback to run immediately
|
|
1278
1394
|
services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
|
|
1279
|
-
if (event === '
|
|
1395
|
+
if (event === 'change:config' || event === 'ready') cb();
|
|
1280
1396
|
});
|
|
1281
1397
|
|
|
1282
1398
|
// Act
|
|
@@ -1284,18 +1400,9 @@ describe('webex-core', () => {
|
|
|
1284
1400
|
await waitForAsync();
|
|
1285
1401
|
|
|
1286
1402
|
// 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
|
-
);
|
|
1403
|
+
assert.isFalse(initSpy.called, 'expected initServiceCatalogs to be skipped with cache present');
|
|
1404
|
+
assert.isTrue(services._getCatalog().status.preauth.ready, 'preauth should be ready from cache');
|
|
1405
|
+
assert.isTrue(services._getCatalog().status.postauth.ready, 'postauth should be ready from cache');
|
|
1299
1406
|
assert.isFalse(cacheSpy.called, 'should not write cache during warm-up-only path');
|
|
1300
1407
|
|
|
1301
1408
|
// Cleanup
|
|
@@ -1311,29 +1418,26 @@ describe('webex-core', () => {
|
|
|
1311
1418
|
preauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1312
1419
|
postauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1313
1420
|
};
|
|
1314
|
-
|
|
1421
|
+
|
|
1315
1422
|
window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify(staleCached));
|
|
1316
|
-
|
|
1423
|
+
|
|
1317
1424
|
const warmed = await services._loadCatalogFromCache();
|
|
1318
|
-
|
|
1425
|
+
|
|
1319
1426
|
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
|
-
);
|
|
1427
|
+
assert.isNull(window.localStorage.getItem(CATALOG_CACHE_KEY_V1), 'expired cache must be cleared');
|
|
1324
1428
|
assert.isFalse(catalog.status.preauth.ready);
|
|
1325
1429
|
assert.isFalse(catalog.status.postauth.ready);
|
|
1326
1430
|
});
|
|
1327
|
-
|
|
1431
|
+
|
|
1328
1432
|
it('clearCatalogCache() removes the cached entry', async () => {
|
|
1329
1433
|
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1330
1434
|
window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify({cachedAt: Date.now()}));
|
|
1331
|
-
|
|
1435
|
+
|
|
1332
1436
|
await services.clearCatalogCache();
|
|
1333
|
-
|
|
1437
|
+
|
|
1334
1438
|
assert.isNull(window.localStorage.getItem(CATALOG_CACHE_KEY_V1), 'cache should be cleared');
|
|
1335
1439
|
});
|
|
1336
|
-
|
|
1440
|
+
|
|
1337
1441
|
it('still fetches when forceRefresh=true even if ready', async () => {
|
|
1338
1442
|
const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
|
|
1339
1443
|
window.localStorage.setItem(
|
|
@@ -1345,24 +1449,20 @@ describe('webex-core', () => {
|
|
|
1345
1449
|
postauth: {serviceLinks: {}, hostCatalog: {}},
|
|
1346
1450
|
})
|
|
1347
1451
|
);
|
|
1348
|
-
|
|
1452
|
+
|
|
1349
1453
|
// warm from cache
|
|
1350
1454
|
const warmed = await services._loadCatalogFromCache();
|
|
1351
1455
|
assert.isTrue(warmed);
|
|
1352
1456
|
assert.isTrue(catalog.status.preauth.ready);
|
|
1353
1457
|
assert.isTrue(catalog.status.postauth.ready);
|
|
1354
|
-
|
|
1458
|
+
|
|
1355
1459
|
const fetchSpy = sinon.spy(services, '_fetchNewServiceHostmap');
|
|
1356
|
-
|
|
1460
|
+
|
|
1357
1461
|
// 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
|
-
});
|
|
1462
|
+
await services.updateServices({from: 'limited', query: {orgId: 'urn:EXAMPLE:org'}, forceRefresh: true});
|
|
1363
1463
|
// pass an empty query to avoid spreading undefined in qs construction
|
|
1364
1464
|
await services.updateServices({forceRefresh: true});
|
|
1365
|
-
|
|
1465
|
+
|
|
1366
1466
|
assert.isTrue(fetchSpy.called, 'forceRefresh should bypass cache short-circuit');
|
|
1367
1467
|
fetchSpy.restore();
|
|
1368
1468
|
});
|
|
@@ -1423,11 +1523,9 @@ describe('webex-core', () => {
|
|
|
1423
1523
|
);
|
|
1424
1524
|
// formatter returns at least one entry to mark ready
|
|
1425
1525
|
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1426
|
-
sinon
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1430
|
-
]);
|
|
1526
|
+
sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
|
|
1527
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1528
|
+
]);
|
|
1431
1529
|
|
|
1432
1530
|
const warmed = await services._loadCatalogFromCache();
|
|
1433
1531
|
assert.isTrue(warmed);
|
|
@@ -1449,11 +1547,9 @@ describe('webex-core', () => {
|
|
|
1449
1547
|
})
|
|
1450
1548
|
);
|
|
1451
1549
|
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1452
|
-
sinon
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1456
|
-
]);
|
|
1550
|
+
sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
|
|
1551
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1552
|
+
]);
|
|
1457
1553
|
|
|
1458
1554
|
const warmed = await services._loadCatalogFromCache();
|
|
1459
1555
|
// function returns true if overall cache path succeeded; we only verify group readiness
|
|
@@ -1480,17 +1576,12 @@ describe('webex-core', () => {
|
|
|
1480
1576
|
})
|
|
1481
1577
|
);
|
|
1482
1578
|
services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
|
|
1483
|
-
sinon
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1487
|
-
]);
|
|
1579
|
+
sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
|
|
1580
|
+
{name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
|
|
1581
|
+
]);
|
|
1488
1582
|
|
|
1489
1583
|
await services._loadCatalogFromCache();
|
|
1490
|
-
assert.isFalse(
|
|
1491
|
-
catalog.status.preauth.ready,
|
|
1492
|
-
'preauth should not warm on selection mismatch'
|
|
1493
|
-
);
|
|
1584
|
+
assert.isFalse(catalog.status.preauth.ready, 'preauth should not warm on selection mismatch');
|
|
1494
1585
|
});
|
|
1495
1586
|
|
|
1496
1587
|
it('skips warming when environment fingerprint mismatches', async () => {
|
|
@@ -1501,10 +1592,7 @@ describe('webex-core', () => {
|
|
|
1501
1592
|
JSON.stringify({
|
|
1502
1593
|
cachedAt: Date.now(),
|
|
1503
1594
|
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
|
-
},
|
|
1595
|
+
preauth: {hostMap: {serviceLinks: {}, hostCatalog: {}}, meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'}},
|
|
1508
1596
|
})
|
|
1509
1597
|
);
|
|
1510
1598
|
// current env
|
|
@@ -1519,6 +1607,7 @@ describe('webex-core', () => {
|
|
|
1519
1607
|
assert.isFalse(catalog.status.postauth.ready);
|
|
1520
1608
|
});
|
|
1521
1609
|
});
|
|
1610
|
+
|
|
1522
1611
|
});
|
|
1523
1612
|
});
|
|
1524
1613
|
/* eslint-enable no-underscore-dangle */
|