@webex/webex-core 3.11.0-webex-services-ready.1 → 3.12.0-llmrefactor.1
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/README.md +5 -2
- package/dist/config.js +15 -0
- package/dist/config.js.map +1 -1
- package/dist/credentials-config.js +12 -0
- package/dist/credentials-config.js.map +1 -1
- package/dist/interceptors/redirect.js +1 -1
- package/dist/interceptors/redirect.js.map +1 -1
- package/dist/lib/batcher.js +23 -7
- package/dist/lib/batcher.js.map +1 -1
- package/dist/lib/credentials/credentials.js +48 -4
- package/dist/lib/credentials/credentials.js.map +1 -1
- package/dist/lib/credentials/token.js +1 -1
- package/dist/lib/domains.js +90 -0
- package/dist/lib/domains.js.map +1 -0
- package/dist/lib/services/service-catalog.js +6 -10
- package/dist/lib/services/service-catalog.js.map +1 -1
- package/dist/lib/services/services.js +208 -51
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/services-v2/service-catalog.js +6 -12
- package/dist/lib/services-v2/service-catalog.js.map +1 -1
- package/dist/lib/services-v2/services-v2.js +207 -46
- 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/dist/webex-core.js.map +1 -1
- package/package.json +13 -13
- package/src/config.js +17 -0
- package/src/credentials-config.js +13 -0
- package/src/interceptors/redirect.js +4 -1
- package/src/lib/batcher.js +25 -10
- package/src/lib/credentials/credentials.js +50 -3
- package/src/lib/domains.ts +94 -0
- package/src/lib/services/service-catalog.js +6 -10
- package/src/lib/services/services.js +174 -37
- package/src/lib/services-v2/service-catalog.ts +6 -11
- package/src/lib/services-v2/services-v2.ts +173 -33
- package/test/fixtures/activation-email.ts +22 -0
- package/test/integration/spec/services/service-catalog.js +7 -6
- package/test/integration/spec/services/services.js +49 -24
- package/test/integration/spec/services-v2/services-v2.js +49 -24
- package/test/unit/spec/credentials/credentials.js +133 -2
- package/test/unit/spec/interceptors/auth.js +56 -0
- package/test/unit/spec/lib/batcher.js +56 -0
- package/test/unit/spec/services/service-catalog.js +93 -11
- package/test/unit/spec/services/services.js +458 -322
- package/test/unit/spec/services-v2/service-catalog.ts +93 -11
- package/test/unit/spec/services-v2/services-v2.ts +403 -214
- package/test/unit/spec/webex-core.js +0 -2
- package/test/unit/spec/webex-internal-core.js +0 -2
|
@@ -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,295 @@ 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, catalogInitTimeout: 15000},
|
|
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
|
+
// `catalog.isReady` is now set inside initServiceCatalogs (after the
|
|
324
|
+
// postauth updateServices) rather than by this listener, so it is not
|
|
325
|
+
// asserted here where initServiceCatalogs is stubbed.
|
|
326
|
+
await waitForAsync();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('does not re-init postauth catalog when canAuthorize fires but catalog is already ready', async () => {
|
|
330
|
+
services.listenToOnce = sinon.stub();
|
|
331
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
332
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
333
|
+
|
|
334
|
+
services.initialize();
|
|
335
|
+
// initialize() creates a new catalog; grab the current one.
|
|
336
|
+
const currentCatalog = services._getCatalog();
|
|
337
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
338
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
339
|
+
await waitForAsync();
|
|
340
|
+
|
|
341
|
+
const freshLoginCall = services.listenToOnce
|
|
342
|
+
.getCalls()
|
|
343
|
+
.find((call: any) => call.args[1] === 'change:canAuthorize');
|
|
344
|
+
|
|
345
|
+
services.webex.canAuthorize = true;
|
|
346
|
+
currentCatalog.status.postauth.ready = true;
|
|
347
|
+
freshLoginCall!.args[2]();
|
|
348
|
+
|
|
349
|
+
assert.isFalse(
|
|
350
|
+
services.initServiceCatalogs.called,
|
|
351
|
+
'must not re-fetch postauth catalog if it is already ready'
|
|
352
|
+
);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('finalizes ready when cache is warm (skips network fetch)', async () => {
|
|
356
|
+
services.listenToOnce = sinon.stub();
|
|
357
|
+
services._loadCatalogFromCache = sinon.stub().returns(Promise.resolve(true));
|
|
358
|
+
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
359
|
+
services.collectPreauthCatalog = sinon.stub().returns(Promise.resolve());
|
|
360
|
+
|
|
361
|
+
services.initialize();
|
|
362
|
+
services.listenToOnce.getCall(0).args[2]();
|
|
363
|
+
services.listenToOnce.getCall(1).args[2]();
|
|
364
|
+
await waitForAsync();
|
|
365
|
+
await waitForAsync();
|
|
366
|
+
|
|
367
|
+
assert.isTrue(services.ready);
|
|
368
|
+
assert.isFalse(
|
|
369
|
+
services.initServiceCatalogs.called,
|
|
370
|
+
'must skip network fetch when cache is warm'
|
|
371
|
+
);
|
|
372
|
+
assert.isFalse(
|
|
373
|
+
services.collectPreauthCatalog.called,
|
|
374
|
+
'must skip preauth fetch when cache is warm'
|
|
375
|
+
);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('#_finalizeReady', () => {
|
|
380
|
+
beforeEach(() => {
|
|
381
|
+
// Outer beforeEach construction already ran initialize() (ungated,
|
|
382
|
+
// sets ready=true). Reset so _finalizeReady's flip is observable.
|
|
383
|
+
services.ready = false;
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('sets ready=true immediately when credentials are not refreshing', async () => {
|
|
387
|
+
services.webex.credentials = {isRefreshing: false};
|
|
388
|
+
assert.isFalse(services.ready);
|
|
389
|
+
|
|
390
|
+
await services._finalizeReady();
|
|
391
|
+
|
|
392
|
+
assert.isTrue(services.ready);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('sets ready=true when credentials are missing', async () => {
|
|
396
|
+
services.webex.credentials = undefined;
|
|
397
|
+
|
|
398
|
+
await services._finalizeReady();
|
|
399
|
+
|
|
400
|
+
assert.isTrue(services.ready);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('awaits change:isRefreshing before setting ready=true', async () => {
|
|
404
|
+
let onChangeIsRefreshing: (() => void) | undefined;
|
|
405
|
+
services.webex.credentials = {
|
|
406
|
+
isRefreshing: true,
|
|
407
|
+
once: sinon.stub().callsFake((event: string, cb: () => void) => {
|
|
408
|
+
if (event === 'change:isRefreshing') onChangeIsRefreshing = cb;
|
|
409
|
+
}),
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const settled = services._finalizeReady();
|
|
413
|
+
await waitForAsync();
|
|
414
|
+
|
|
415
|
+
assert.isFalse(services.ready, 'ready must not flip while refresh is in flight');
|
|
416
|
+
assert.isFunction(onChangeIsRefreshing);
|
|
417
|
+
|
|
418
|
+
onChangeIsRefreshing!();
|
|
419
|
+
await settled;
|
|
420
|
+
|
|
421
|
+
assert.isTrue(services.ready);
|
|
274
422
|
});
|
|
275
423
|
});
|
|
276
424
|
|
|
@@ -425,7 +573,7 @@ describe('webex-core', () => {
|
|
|
425
573
|
|
|
426
574
|
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
427
575
|
services.webex.credentials = {
|
|
428
|
-
getOrgId: sinon.stub().returns('')
|
|
576
|
+
getOrgId: sinon.stub().returns('')
|
|
429
577
|
};
|
|
430
578
|
catalog.status = {};
|
|
431
579
|
});
|
|
@@ -466,7 +614,7 @@ describe('webex-core', () => {
|
|
|
466
614
|
const serviceGroup = 'postauth';
|
|
467
615
|
const hostmap = {services: [{hostmap: 'hostmap'}]};
|
|
468
616
|
|
|
469
|
-
services._formatReceivedHostmap = sinon.stub().returns({services: [{some: 'hostmap'}]});
|
|
617
|
+
services._formatReceivedHostmap = sinon.stub().returns({services : [{some: 'hostmap'}]});
|
|
470
618
|
|
|
471
619
|
catalog.updateServiceGroups = sinon.stub().returns(Promise.resolve([{some: 'value'}]));
|
|
472
620
|
|
|
@@ -482,7 +630,7 @@ describe('webex-core', () => {
|
|
|
482
630
|
const serviceGroup = 'postauth';
|
|
483
631
|
const hostmap = {};
|
|
484
632
|
|
|
485
|
-
services._formatReceivedHostmap = sinon.stub().returns({services: undefined});
|
|
633
|
+
services._formatReceivedHostmap = sinon.stub().returns({services : undefined});
|
|
486
634
|
|
|
487
635
|
catalog.updateServiceGroups = sinon.stub().returns(Promise.resolve([{some: 'value'}]));
|
|
488
636
|
|
|
@@ -527,6 +675,22 @@ describe('webex-core', () => {
|
|
|
527
675
|
);
|
|
528
676
|
});
|
|
529
677
|
|
|
678
|
+
it('includes the catalog override query when configured', async () => {
|
|
679
|
+
services.webex.config.services = {useCatalogOverride: true};
|
|
680
|
+
sinon.stub(services, '_formatReceivedHostmap').resolves('map response');
|
|
681
|
+
sinon.stub(services, 'request').resolves({});
|
|
682
|
+
|
|
683
|
+
await services._fetchNewServiceHostmap({from: 'limited'});
|
|
684
|
+
|
|
685
|
+
assert.calledOnceWithExactly(services.request, {
|
|
686
|
+
method: 'GET',
|
|
687
|
+
service: 'u2c',
|
|
688
|
+
resource: '/limited/catalog',
|
|
689
|
+
qs: {format: 'U2CV2', useCatalogOverride: true},
|
|
690
|
+
headers: {},
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
|
|
530
694
|
it('checks service request rejects', async () => {
|
|
531
695
|
const error = new Error('some error');
|
|
532
696
|
|
|
@@ -721,13 +885,13 @@ describe('webex-core', () => {
|
|
|
721
885
|
});
|
|
722
886
|
|
|
723
887
|
describe('#invalidateCache', () => {
|
|
724
|
-
beforeEach(() => {
|
|
888
|
+
beforeEach( () => {
|
|
725
889
|
services.initServiceCatalogs = sinon.stub().returns(Promise.resolve());
|
|
726
890
|
services.webex.credentials = {
|
|
727
|
-
getOrgId: sinon.stub().returns('')
|
|
891
|
+
getOrgId: sinon.stub().returns('')
|
|
728
892
|
};
|
|
729
893
|
catalog.status = {};
|
|
730
|
-
})
|
|
894
|
+
})
|
|
731
895
|
it('should log the timestamp parameter', async () => {
|
|
732
896
|
const timestamp = '1234567890';
|
|
733
897
|
services.logger.info = sinon.stub();
|
|
@@ -735,11 +899,7 @@ describe('webex-core', () => {
|
|
|
735
899
|
|
|
736
900
|
await services.invalidateCache(timestamp);
|
|
737
901
|
|
|
738
|
-
assert.calledWith(
|
|
739
|
-
services.logger.info,
|
|
740
|
-
'services: invalidate cache, timestamp:',
|
|
741
|
-
timestamp
|
|
742
|
-
);
|
|
902
|
+
assert.calledWith(services.logger.info, 'services: invalidate cache, timestamp:', timestamp);
|
|
743
903
|
});
|
|
744
904
|
|
|
745
905
|
it('should call initServiceCatalogs when invalidate timestamp is newer than catalog timestamp', async () => {
|
|
@@ -866,30 +1026,30 @@ describe('webex-core', () => {
|
|
|
866
1026
|
// Arrange: seed internal _services with mobius (including duplicate baseUrl)
|
|
867
1027
|
services._services = [
|
|
868
1028
|
{
|
|
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
|
|
1029
|
+
"id": "urn:TEAM:us-east-2_a:mobius",
|
|
1030
|
+
"serviceName": 'mobius',
|
|
1031
|
+
"serviceUrls": [
|
|
1032
|
+
{"baseUrl": 'https://mobius-us-east-2.prod.infra.webex.com/api/v1', "priority": 5},
|
|
1033
|
+
{"baseUrl": 'https://mobius-eu-central-1.prod.infra.webex.com/api/v1', "priority": 10},
|
|
1034
|
+
{"baseUrl": 'https://mobius-ap-southeast-2.prod.infra.webex.com/api/v1', "priority": 15}, // duplicate
|
|
875
1035
|
],
|
|
876
1036
|
},
|
|
877
1037
|
{
|
|
878
|
-
id:
|
|
879
|
-
serviceName:
|
|
880
|
-
serviceUrls: [
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1038
|
+
"id": "urn:TEAM:ap-southeast-2_m:mobius",
|
|
1039
|
+
"serviceName": "mobius",
|
|
1040
|
+
"serviceUrls": [
|
|
1041
|
+
{
|
|
1042
|
+
"baseUrl": "https://mobius-me-central-1.prod.infra.webex.com/api/v1",
|
|
1043
|
+
"priority": 5
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
"baseUrl": "https://mobius-eu-central-1.prod.infra.webex.com/api/v1",
|
|
1047
|
+
"priority": 10
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
"baseUrl": "https://mobius-ap-southeast-2.prod.infra.webex.com/api/v1",
|
|
1051
|
+
"priority": 15
|
|
1052
|
+
},
|
|
893
1053
|
],
|
|
894
1054
|
},
|
|
895
1055
|
// Non-mobius service should be ignored by getMobiusClusters
|
|
@@ -907,64 +1067,42 @@ describe('webex-core', () => {
|
|
|
907
1067
|
assert.deepEqual(
|
|
908
1068
|
clusters.map(({host, id, ttl, priority}) => ({host, id, ttl, priority})),
|
|
909
1069
|
[
|
|
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
|
-
},
|
|
1070
|
+
{host: 'mobius-us-east-2.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 5},
|
|
1071
|
+
{host: 'mobius-eu-central-1.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 10},
|
|
1072
|
+
{host: 'mobius-ap-southeast-2.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 15},
|
|
1073
|
+
{host: 'mobius-me-central-1.prod.infra.webex.com', id: 'urn:TEAM:ap-southeast-2_m:mobius', ttl: 0, priority: 5},
|
|
934
1074
|
]
|
|
935
1075
|
);
|
|
936
1076
|
});
|
|
937
1077
|
});
|
|
938
|
-
|
|
1078
|
+
|
|
939
1079
|
describe('#isValidHost', () => {
|
|
940
1080
|
beforeEach(() => {
|
|
941
1081
|
// 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
|
-
];
|
|
1082
|
+
services._services = [{
|
|
1083
|
+
"id": "urn:IDENTITY:PC75:adminAudit",
|
|
1084
|
+
"serviceName": "adminAudit",
|
|
1085
|
+
"serviceUrls": [
|
|
1086
|
+
{
|
|
1087
|
+
"baseUrl": "https://audit-ci-r.wbx2.com/audit-ci/api/v2",
|
|
1088
|
+
"priority": 5
|
|
1089
|
+
},
|
|
1090
|
+
{
|
|
1091
|
+
"baseUrl": "https://audit-ci-t.wbx2.com/audit-ci/api/v2",
|
|
1092
|
+
"priority": 10
|
|
1093
|
+
}
|
|
1094
|
+
]
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
"id": "urn:IDENTITY:PC75:cdf",
|
|
1098
|
+
"serviceName": "cdf",
|
|
1099
|
+
"serviceUrls": [
|
|
1100
|
+
{
|
|
1101
|
+
"baseUrl": "https://wapdavis.webex.com/davis/api/v1",
|
|
1102
|
+
"priority": 5
|
|
1103
|
+
}
|
|
1104
|
+
]
|
|
1105
|
+
}];
|
|
968
1106
|
});
|
|
969
1107
|
afterAll(() => {
|
|
970
1108
|
// Clean up the mock services list
|
|
@@ -988,6 +1126,57 @@ describe('webex-core', () => {
|
|
|
988
1126
|
});
|
|
989
1127
|
});
|
|
990
1128
|
|
|
1129
|
+
describe('#isIntegrationEnvironment', () => {
|
|
1130
|
+
it('returns true when u2c URL contains "intb"', () => {
|
|
1131
|
+
services.webex.config = {
|
|
1132
|
+
services: {
|
|
1133
|
+
discovery: {
|
|
1134
|
+
u2c: 'https://u2c-intb.ciscospark.com/u2c/api/v1',
|
|
1135
|
+
},
|
|
1136
|
+
},
|
|
1137
|
+
};
|
|
1138
|
+
assert.isTrue(services.isIntegrationEnvironment());
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
it('returns false when u2c URL does not contain "intb" (production)', () => {
|
|
1142
|
+
services.webex.config = {
|
|
1143
|
+
services: {
|
|
1144
|
+
discovery: {
|
|
1145
|
+
u2c: 'https://u2c.wbx2.com/u2c/api/v1',
|
|
1146
|
+
},
|
|
1147
|
+
},
|
|
1148
|
+
};
|
|
1149
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
it('returns false when u2c URL is for FedRAMP', () => {
|
|
1153
|
+
services.webex.config = {
|
|
1154
|
+
services: {
|
|
1155
|
+
discovery: {
|
|
1156
|
+
u2c: 'https://u2c.gov.ciscospark.com/u2c/api/v1',
|
|
1157
|
+
},
|
|
1158
|
+
},
|
|
1159
|
+
};
|
|
1160
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
it('returns false when u2c URL is undefined', () => {
|
|
1164
|
+
services.webex.config = {
|
|
1165
|
+
services: {
|
|
1166
|
+
discovery: {
|
|
1167
|
+
u2c: undefined,
|
|
1168
|
+
},
|
|
1169
|
+
},
|
|
1170
|
+
};
|
|
1171
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
it('returns false when config is not available', () => {
|
|
1175
|
+
services.webex.config = undefined;
|
|
1176
|
+
assert.isFalse(services.isIntegrationEnvironment());
|
|
1177
|
+
});
|
|
1178
|
+
});
|
|
1179
|
+
|
|
991
1180
|
describe('U2C catalog cache behavior (v2)', () => {
|
|
992
1181
|
const CATALOG_CACHE_KEY_V2 = 'services.v2.u2cHostMap';
|
|
993
1182
|
let windowBackup;
|