@webex/webex-core 3.12.0-next.8 → 3.12.0-task-refactor.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.
@@ -349,6 +349,8 @@ describe('webex-core', () => {
349
349
 
350
350
  const mapResult = await services._fetchNewServiceHostmap({from: 'limited'});
351
351
 
352
+ assert.deepEqual(mapResult, mapResponse);
353
+
352
354
  assert.calledOnceWithExactly(services.request, {
353
355
  method: 'GET',
354
356
  service: 'u2c',
@@ -842,17 +844,14 @@ describe('webex-core', () => {
842
844
  it('returns unique mobius host entries from hostCatalog', () => {
843
845
  // Arrange: two hostCatalog keys, with duplicate mobius host across keys
844
846
  services._hostCatalog = {
845
- 'mobius-us-east-2.prod.infra.webex.com': [
846
- {host: 'mobius-us-east-2.prod.infra.webex.com', ttl: -1, priority: 5, id: 'urn:TEAM:xyz:mobius'},
847
- {host: 'mobius-eu-central-1.prod.infra.webex.com', ttl: -1, priority: 10, id: 'urn:TEAM:xyz:mobius'},
848
- ],
849
-
850
- 'mobius-eu-central-1.prod.infra.webex.com': [
851
- {host: 'mobius-us-east-2.prod.infra.webex.com', ttl: -1, priority: 7, id: 'urn:TEAM:xyz:mobius'}, // duplicate host
852
- ],
853
- 'wdm-a.webex.com' : [
854
- {host: 'wdm-a.webex.com', ttl: -1, priority: 5, id: 'urn:TEAM:xyz:wdm'},
855
- ]
847
+ 'mobius-a.webex.com': [
848
+ {host: 'mobius-a.webex.com', ttl: -1, priority: 5, id: 'urn:TEAM:xyz:mobius'},
849
+ {host: 'mobius-b.webex.com', ttl: -1, priority: 10, id: 'urn:TEAM:xyz:mobius'},
850
+ {host: 'ignore.webex.com', ttl: -1, priority: 1, id: 'urn:TEAM:abc:wdm'}, // not mobius
851
+ ],
852
+ 'dup-entry-key': [
853
+ {host: 'mobius-a.webex.com', ttl: -1, priority: 7, id: 'urn:TEAM:xyz:mobius'}, // duplicate host
854
+ ],
856
855
  };
857
856
 
858
857
  // Act
@@ -863,13 +862,12 @@ describe('webex-core', () => {
863
862
  assert.deepEqual(
864
863
  clusters.map(({host, id, ttl, priority}) => ({host, id, ttl, priority})),
865
864
  [
866
- {host: 'mobius-us-east-2.prod.infra.webex.com', id: 'urn:TEAM:xyz:mobius', ttl: -1, priority: 5},
867
- {host: 'mobius-eu-central-1.prod.infra.webex.com', id: 'urn:TEAM:xyz:mobius', ttl: -1, priority: 10},
865
+ {host: 'mobius-a.webex.com', id: 'urn:TEAM:xyz:mobius', ttl: -1, priority: 5},
866
+ {host: 'mobius-b.webex.com', id: 'urn:TEAM:xyz:mobius', ttl: -1, priority: 10},
868
867
  ]
869
868
  );
870
869
  });
871
870
  });
872
-
873
871
  describe('#isValidHost', () => {
874
872
  beforeEach(() => {
875
873
  // Setting up a mock host catalog
@@ -921,384 +919,6 @@ describe('webex-core', () => {
921
919
  assert.isFalse(services.isValidHost([]));
922
920
  });
923
921
  });
924
-
925
- describe('#isIntegrationEnvironment', () => {
926
- it('returns true when u2c URL contains "intb"', () => {
927
- services.webex.config = {
928
- services: {
929
- discovery: {
930
- u2c: 'https://u2c-intb.ciscospark.com/u2c/api/v1',
931
- },
932
- },
933
- };
934
- assert.isTrue(services.isIntegrationEnvironment());
935
- });
936
-
937
- it('returns false when u2c URL does not contain "intb" (production)', () => {
938
- services.webex.config = {
939
- services: {
940
- discovery: {
941
- u2c: 'https://u2c.wbx2.com/u2c/api/v1',
942
- },
943
- },
944
- };
945
- assert.isFalse(services.isIntegrationEnvironment());
946
- });
947
-
948
- it('returns false when u2c URL is for FedRAMP', () => {
949
- services.webex.config = {
950
- services: {
951
- discovery: {
952
- u2c: 'https://u2c.gov.ciscospark.com/u2c/api/v1',
953
- },
954
- },
955
- };
956
- assert.isFalse(services.isIntegrationEnvironment());
957
- });
958
-
959
- it('returns false when u2c URL is undefined', () => {
960
- services.webex.config = {
961
- services: {
962
- discovery: {
963
- u2c: undefined,
964
- },
965
- },
966
- };
967
- assert.isFalse(services.isIntegrationEnvironment());
968
- });
969
-
970
- it('returns false when config is not available', () => {
971
- services.webex.config = undefined;
972
- assert.isFalse(services.isIntegrationEnvironment());
973
- });
974
- });
975
-
976
- describe('U2C catalog cache behavior', () => {
977
- let webex;
978
- let services;
979
- let catalog;
980
- let localStorageBackup;
981
- let windowBackup;
982
-
983
- const makeLocalStorageShim = () => {
984
- const store = new Map();
985
- return {
986
- getItem: (k) => (store.has(k) ? store.get(k) : null),
987
- setItem: (k, v) => store.set(k, v),
988
- removeItem: (k) => store.delete(k),
989
- _store: store,
990
- };
991
- };
992
-
993
- beforeEach(() => {
994
- // Build a fresh webex instance
995
- webex = new MockWebex({children: {services: Services}, config: {credentials: {federation: true}}});
996
- services = webex.internal.services;
997
- catalog = services._getCatalog();
998
-
999
- // enable U2C caching feature flag in tests that rely on localStorage writes/reads
1000
- services.webex.config = services.webex.config || {};
1001
- services.webex.config.calling = {...(services.webex.config.calling || {}), cacheU2C: true};
1002
-
1003
- // stub window.localStorage
1004
- windowBackup = global.window;
1005
- if (!global.window) global.window = {};
1006
- localStorageBackup = global.window.localStorage;
1007
- global.window.localStorage = makeLocalStorageShim();
1008
- // Ensure code under test uses our shim via util method
1009
- sinon.stub(services, '_getLocalStorageSafe').returns(global.window.localStorage);
1010
-
1011
- // Stub the formatter so we don't need a full hostmap payload in tests
1012
- sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
1013
- {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1014
- ]);
1015
- });
1016
-
1017
- afterEach(() => {
1018
- global.window.localStorage = localStorageBackup || undefined;
1019
- if (!windowBackup) {
1020
- delete global.window;
1021
- } else {
1022
- global.window = windowBackup;
1023
- }
1024
- // Restore util stub if present
1025
- if (services._getLocalStorageSafe && services._getLocalStorageSafe.restore) {
1026
- services._getLocalStorageSafe.restore();
1027
- }
1028
- });
1029
-
1030
- it('invokes initServiceCatalogs on ready, caches catalog, and stores in localStorage', async () => {
1031
- // Arrange: authenticated credentials and spies
1032
- services.webex.credentials = {
1033
- getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
1034
- canAuthorize: true,
1035
- supertoken: {access_token: 'token'},
1036
- };
1037
- const initSpy = sinon.spy(services, 'initServiceCatalogs');
1038
- const cacheSpy = sinon.spy(services, '_cacheCatalog');
1039
- const setItemSpy = sinon.spy(global.window.localStorage, 'setItem');
1040
- // Make fetch return a hostmap object and allow formatter to reduce it
1041
- sinon.stub(services, 'request').resolves({body: {services: [], activeServices: {}, timestamp: Date.now().toString(), orgId: 'urn:EXAMPLE:org', format: 'U2CV2'}});
1042
- // Cause ready callback to run immediately
1043
- services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
1044
- if (event === 'ready') cb();
1045
- });
1046
-
1047
- // Act
1048
- services.initialize();
1049
- await waitForAsync();
1050
-
1051
- // Assert: initServiceCatalogs was called because there was no cache
1052
- assert.isTrue(initSpy.called, 'expected initServiceCatalogs to be invoked on ready');
1053
- // _cacheCatalog is called at least once (preauth/postauth flows)
1054
- assert.isTrue(cacheSpy.called, 'expected _cacheCatalog to be called');
1055
- assert.isTrue(setItemSpy.called, 'expected localStorage.setItem to be called');
1056
-
1057
- // Cleanup spies
1058
- services.request.restore();
1059
- initSpy.restore();
1060
- cacheSpy.restore();
1061
- setItemSpy.restore();
1062
- });
1063
-
1064
- it('does not invoke initServiceCatalogs on ready when cache exists and uses cached catalog', async () => {
1065
- // Arrange: put a valid cache
1066
- const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
1067
- const cached = {
1068
- orgId: 'urn:EXAMPLE:org',
1069
- cachedAt: Date.now(),
1070
- preauth: {serviceLinks: {}, hostCatalog: {}},
1071
- postauth: {serviceLinks: {}, hostCatalog: {}},
1072
- };
1073
- global.window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify(cached));
1074
-
1075
- // authenticated credentials
1076
- services.webex.credentials = {
1077
- getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
1078
- canAuthorize: true,
1079
- supertoken: {access_token: 'token'},
1080
- };
1081
-
1082
- const initSpy = sinon.spy(services, 'initServiceCatalogs');
1083
- const cacheSpy = sinon.spy(services, '_cacheCatalog');
1084
- // Cause ready callback to run immediately
1085
- services.listenToOnce = sinon.stub().callsFake((ctx, event, cb) => {
1086
- if (event === 'ready') cb();
1087
- });
1088
-
1089
- // Act
1090
- services.initialize();
1091
- await waitForAsync();
1092
-
1093
- // Assert: ready path found cache and skipped initServiceCatalogs
1094
- assert.isFalse(initSpy.called, 'expected initServiceCatalogs to be skipped with cache present');
1095
- assert.isTrue(services._getCatalog().status.preauth.ready, 'preauth should be ready from cache');
1096
- assert.isTrue(services._getCatalog().status.postauth.ready, 'postauth should be ready from cache');
1097
- assert.isFalse(cacheSpy.called, 'should not write cache during warm-up-only path');
1098
-
1099
- // Cleanup
1100
- initSpy.restore();
1101
- cacheSpy.restore();
1102
- });
1103
-
1104
- it('expires cached catalog after TTL and clears the entry', async () => {
1105
- const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
1106
- const staleCached = {
1107
- orgId: 'urn:EXAMPLE:org',
1108
- cachedAt: Date.now() - (24 * 60 * 60 * 1000 + 1000), // past TTL
1109
- preauth: {serviceLinks: {}, hostCatalog: {}},
1110
- postauth: {serviceLinks: {}, hostCatalog: {}},
1111
- };
1112
-
1113
- window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify(staleCached));
1114
-
1115
- const warmed = await services._loadCatalogFromCache();
1116
-
1117
- assert.isFalse(warmed, 'stale cache must not warm');
1118
- assert.isNull(window.localStorage.getItem(CATALOG_CACHE_KEY_V1), 'expired cache must be cleared');
1119
- assert.isFalse(catalog.status.preauth.ready);
1120
- assert.isFalse(catalog.status.postauth.ready);
1121
- });
1122
-
1123
- it('clearCatalogCache() removes the cached entry', async () => {
1124
- const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
1125
- window.localStorage.setItem(CATALOG_CACHE_KEY_V1, JSON.stringify({cachedAt: Date.now()}));
1126
-
1127
- await services.clearCatalogCache();
1128
-
1129
- assert.isNull(window.localStorage.getItem(CATALOG_CACHE_KEY_V1), 'cache should be cleared');
1130
- });
1131
-
1132
- it('still fetches when forceRefresh=true even if ready', async () => {
1133
- const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
1134
- window.localStorage.setItem(
1135
- CATALOG_CACHE_KEY_V1,
1136
- JSON.stringify({
1137
- orgId: 'urn:EXAMPLE:org',
1138
- cachedAt: Date.now(),
1139
- preauth: {serviceLinks: {}, hostCatalog: {}},
1140
- postauth: {serviceLinks: {}, hostCatalog: {}},
1141
- })
1142
- );
1143
-
1144
- // warm from cache
1145
- const warmed = await services._loadCatalogFromCache();
1146
- assert.isTrue(warmed);
1147
- assert.isTrue(catalog.status.preauth.ready);
1148
- assert.isTrue(catalog.status.postauth.ready);
1149
-
1150
- const fetchSpy = sinon.spy(services, '_fetchNewServiceHostmap');
1151
-
1152
- // with forceRefresh we should fetch despite ready=true
1153
- await services.updateServices({from: 'limited', query: {orgId: 'urn:EXAMPLE:org'}, forceRefresh: true});
1154
- // pass an empty query to avoid spreading undefined in qs construction
1155
- await services.updateServices({forceRefresh: true});
1156
-
1157
- assert.isTrue(fetchSpy.called, 'forceRefresh should bypass cache short-circuit');
1158
- fetchSpy.restore();
1159
- });
1160
-
1161
- it('stores selection metadata and env on cache write for preauth', async () => {
1162
- const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
1163
- // arrange config for env fingerprint
1164
- services.webex.config = services.webex.config || {};
1165
- services.webex.config.services = services.webex.config.services || {discovery: {}};
1166
- services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
1167
- services.webex.config.fedramp = false;
1168
-
1169
- // write cache with meta
1170
- await services._cacheCatalog(
1171
- 'preauth',
1172
- {serviceLinks: {}, hostCatalog: {}},
1173
- {selectionType: 'orgId', selectionValue: 'urn:EXAMPLE:org'}
1174
- );
1175
-
1176
- const raw = window.localStorage.getItem(CATALOG_CACHE_KEY_V1);
1177
- assert.isString(raw);
1178
- const parsed = JSON.parse(raw);
1179
- assert.equal(parsed.orgId, undefined, 'orgId not set without credentials');
1180
- assert.deepEqual(parsed.env, {
1181
- fedramp: false,
1182
- u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1',
1183
- });
1184
- assert.isObject(parsed.preauth);
1185
- assert.deepEqual(parsed.preauth.meta, {
1186
- selectionType: 'orgId',
1187
- selectionValue: 'urn:EXAMPLE:org',
1188
- });
1189
- });
1190
-
1191
- it('warms preauth from cache when selection meta matches intended orgId', async () => {
1192
- const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
1193
- // stub credentials
1194
- services.webex.credentials = {
1195
- canAuthorize: true,
1196
- getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
1197
- };
1198
- // set current env to match cached env
1199
- services.webex.config = services.webex.config || {};
1200
- services.webex.config.services = services.webex.config.services || {discovery: {}};
1201
- services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
1202
- services.webex.config.fedramp = false;
1203
- // cache with matching orgId selection
1204
- window.localStorage.setItem(
1205
- CATALOG_CACHE_KEY_V1,
1206
- JSON.stringify({
1207
- cachedAt: Date.now(),
1208
- env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
1209
- preauth: {
1210
- hostMap: {serviceLinks: {}, hostCatalog: {}},
1211
- meta: {selectionType: 'orgId', selectionValue: 'urn:EXAMPLE:org'},
1212
- },
1213
- })
1214
- );
1215
- // formatter returns at least one entry to mark ready
1216
- services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
1217
- sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
1218
- {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1219
- ]);
1220
-
1221
- const warmed = await services._loadCatalogFromCache();
1222
- assert.isTrue(warmed);
1223
- assert.isTrue(catalog.status.preauth.ready, 'preauth should be warmed on match');
1224
- });
1225
-
1226
- it('does not warm preauth when selection meta is proximity mode', async () => {
1227
- const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
1228
- // cache with proximity mode selection
1229
- window.localStorage.setItem(
1230
- CATALOG_CACHE_KEY_V1,
1231
- JSON.stringify({
1232
- cachedAt: Date.now(),
1233
- env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
1234
- preauth: {
1235
- hostMap: {serviceLinks: {}, hostCatalog: {}},
1236
- meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'},
1237
- },
1238
- })
1239
- );
1240
- services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
1241
- sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
1242
- {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1243
- ]);
1244
-
1245
- const warmed = await services._loadCatalogFromCache();
1246
- // function returns true if overall cache path succeeded; we only verify group readiness
1247
- assert.isFalse(catalog.status.preauth.ready, 'preauth should not warm for proximity mode');
1248
- });
1249
-
1250
- it('does not warm preauth when selection meta mismatches intended selection', async () => {
1251
- const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
1252
- // authorized with org X
1253
- services.webex.credentials = {
1254
- canAuthorize: true,
1255
- getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
1256
- };
1257
- // cache points to a different org
1258
- window.localStorage.setItem(
1259
- CATALOG_CACHE_KEY_V1,
1260
- JSON.stringify({
1261
- cachedAt: Date.now(),
1262
- env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
1263
- preauth: {
1264
- hostMap: {serviceLinks: {}, hostCatalog: {}},
1265
- meta: {selectionType: 'orgId', selectionValue: 'urn:DIFF:org'},
1266
- },
1267
- })
1268
- );
1269
- services._formatReceivedHostmap.restore && services._formatReceivedHostmap.restore();
1270
- sinon.stub(services, '_formatReceivedHostmap').callsFake(() => [
1271
- {name: 'hydra', defaultUrl: 'https://api.ciscospark.com/v1', hosts: []},
1272
- ]);
1273
-
1274
- await services._loadCatalogFromCache();
1275
- assert.isFalse(catalog.status.preauth.ready, 'preauth should not warm on selection mismatch');
1276
- });
1277
-
1278
- it('skips warming when environment fingerprint mismatches', async () => {
1279
- const CATALOG_CACHE_KEY_V1 = 'services.v1.u2cHostMap';
1280
- // cached env differs from current env (different U2C URL)
1281
- window.localStorage.setItem(
1282
- CATALOG_CACHE_KEY_V1,
1283
- JSON.stringify({
1284
- cachedAt: Date.now(),
1285
- env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.other.com/u2c/api/v1'},
1286
- preauth: {hostMap: {serviceLinks: {}, hostCatalog: {}}, meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'}},
1287
- })
1288
- );
1289
- // current env
1290
- services.webex.config = services.webex.config || {};
1291
- services.webex.config.services = services.webex.config.services || {discovery: {}};
1292
- services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
1293
- services.webex.config.fedramp = false;
1294
-
1295
- const warmed = await services._loadCatalogFromCache();
1296
- assert.isFalse(warmed, 'env mismatch should skip warm and return false');
1297
- assert.isFalse(catalog.status.preauth.ready);
1298
- assert.isFalse(catalog.status.postauth.ready);
1299
- });
1300
- });
1301
-
1302
922
  });
1303
923
  });
1304
924
  /* eslint-enable no-underscore-dangle */