@webex/plugin-meetings 3.0.0-beta.39 → 3.0.0-beta.391

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.
Files changed (393) hide show
  1. package/README.md +58 -8
  2. package/dist/annotation/annotation.types.js +7 -0
  3. package/dist/annotation/annotation.types.js.map +1 -0
  4. package/dist/annotation/constants.js +49 -0
  5. package/dist/annotation/constants.js.map +1 -0
  6. package/dist/annotation/index.js +342 -0
  7. package/dist/annotation/index.js.map +1 -0
  8. package/dist/breakouts/breakout.js +94 -15
  9. package/dist/breakouts/breakout.js.map +1 -1
  10. package/dist/breakouts/events.js +45 -0
  11. package/dist/breakouts/events.js.map +1 -0
  12. package/dist/breakouts/index.js +671 -81
  13. package/dist/breakouts/index.js.map +1 -1
  14. package/dist/breakouts/utils.js +45 -1
  15. package/dist/breakouts/utils.js.map +1 -1
  16. package/dist/common/errors/no-meeting-info.js +51 -0
  17. package/dist/common/errors/no-meeting-info.js.map +1 -0
  18. package/dist/common/errors/reclaim-host-role-errors.js +158 -0
  19. package/dist/common/errors/reclaim-host-role-errors.js.map +1 -0
  20. package/dist/common/errors/webex-errors.js +48 -7
  21. package/dist/common/errors/webex-errors.js.map +1 -1
  22. package/dist/common/logs/logger-proxy.js +1 -1
  23. package/dist/common/logs/logger-proxy.js.map +1 -1
  24. package/dist/common/logs/request.js +5 -1
  25. package/dist/common/logs/request.js.map +1 -1
  26. package/dist/common/queue.js +24 -9
  27. package/dist/common/queue.js.map +1 -1
  28. package/dist/config.js +5 -10
  29. package/dist/config.js.map +1 -1
  30. package/dist/constants.js +242 -33
  31. package/dist/constants.js.map +1 -1
  32. package/dist/controls-options-manager/enums.js +14 -2
  33. package/dist/controls-options-manager/enums.js.map +1 -1
  34. package/dist/controls-options-manager/index.js +109 -15
  35. package/dist/controls-options-manager/index.js.map +1 -1
  36. package/dist/controls-options-manager/types.js +7 -0
  37. package/dist/controls-options-manager/types.js.map +1 -0
  38. package/dist/controls-options-manager/util.js +309 -18
  39. package/dist/controls-options-manager/util.js.map +1 -1
  40. package/dist/index.js +110 -2
  41. package/dist/index.js.map +1 -1
  42. package/dist/interceptors/index.js +15 -0
  43. package/dist/interceptors/index.js.map +1 -0
  44. package/dist/interceptors/locusRetry.js +93 -0
  45. package/dist/interceptors/locusRetry.js.map +1 -0
  46. package/dist/interpretation/collection.js +23 -0
  47. package/dist/interpretation/collection.js.map +1 -0
  48. package/dist/interpretation/index.js +380 -0
  49. package/dist/interpretation/index.js.map +1 -0
  50. package/dist/interpretation/siLanguage.js +25 -0
  51. package/dist/interpretation/siLanguage.js.map +1 -0
  52. package/dist/locus-info/controlsUtils.js +91 -2
  53. package/dist/locus-info/controlsUtils.js.map +1 -1
  54. package/dist/locus-info/index.js +386 -62
  55. package/dist/locus-info/index.js.map +1 -1
  56. package/dist/locus-info/infoUtils.js +7 -1
  57. package/dist/locus-info/infoUtils.js.map +1 -1
  58. package/dist/locus-info/mediaSharesUtils.js +71 -1
  59. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  60. package/dist/locus-info/parser.js +249 -72
  61. package/dist/locus-info/parser.js.map +1 -1
  62. package/dist/locus-info/selfUtils.js +89 -14
  63. package/dist/locus-info/selfUtils.js.map +1 -1
  64. package/dist/media/index.js +65 -102
  65. package/dist/media/index.js.map +1 -1
  66. package/dist/media/properties.js +73 -124
  67. package/dist/media/properties.js.map +1 -1
  68. package/dist/mediaQualityMetrics/config.js +135 -330
  69. package/dist/mediaQualityMetrics/config.js.map +1 -1
  70. package/dist/meeting/in-meeting-actions.js +86 -2
  71. package/dist/meeting/in-meeting-actions.js.map +1 -1
  72. package/dist/meeting/index.js +4075 -2827
  73. package/dist/meeting/index.js.map +1 -1
  74. package/dist/meeting/locusMediaRequest.js +292 -0
  75. package/dist/meeting/locusMediaRequest.js.map +1 -0
  76. package/dist/meeting/muteState.js +224 -136
  77. package/dist/meeting/muteState.js.map +1 -1
  78. package/dist/meeting/request.js +177 -152
  79. package/dist/meeting/request.js.map +1 -1
  80. package/dist/meeting/util.js +672 -417
  81. package/dist/meeting/util.js.map +1 -1
  82. package/dist/meeting-info/index.js +73 -7
  83. package/dist/meeting-info/index.js.map +1 -1
  84. package/dist/meeting-info/meeting-info-v2.js +192 -51
  85. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  86. package/dist/meeting-info/util.js +1 -1
  87. package/dist/meeting-info/util.js.map +1 -1
  88. package/dist/meeting-info/utilv2.js +36 -36
  89. package/dist/meeting-info/utilv2.js.map +1 -1
  90. package/dist/meetings/collection.js +39 -0
  91. package/dist/meetings/collection.js.map +1 -1
  92. package/dist/meetings/index.js +484 -119
  93. package/dist/meetings/index.js.map +1 -1
  94. package/dist/meetings/meetings.types.js +7 -0
  95. package/dist/meetings/meetings.types.js.map +1 -0
  96. package/dist/meetings/request.js +2 -0
  97. package/dist/meetings/request.js.map +1 -1
  98. package/dist/meetings/util.js +73 -7
  99. package/dist/meetings/util.js.map +1 -1
  100. package/dist/member/index.js +58 -0
  101. package/dist/member/index.js.map +1 -1
  102. package/dist/member/types.js +25 -0
  103. package/dist/member/types.js.map +1 -0
  104. package/dist/member/util.js +132 -25
  105. package/dist/member/util.js.map +1 -1
  106. package/dist/members/collection.js +10 -0
  107. package/dist/members/collection.js.map +1 -1
  108. package/dist/members/index.js +102 -6
  109. package/dist/members/index.js.map +1 -1
  110. package/dist/members/request.js +106 -38
  111. package/dist/members/request.js.map +1 -1
  112. package/dist/members/types.js +15 -0
  113. package/dist/members/types.js.map +1 -0
  114. package/dist/members/util.js +326 -232
  115. package/dist/members/util.js.map +1 -1
  116. package/dist/metrics/constants.js +18 -1
  117. package/dist/metrics/constants.js.map +1 -1
  118. package/dist/metrics/index.js +1 -446
  119. package/dist/metrics/index.js.map +1 -1
  120. package/dist/multistream/mediaRequestManager.js +223 -32
  121. package/dist/multistream/mediaRequestManager.js.map +1 -1
  122. package/dist/multistream/receiveSlot.js +10 -0
  123. package/dist/multistream/receiveSlot.js.map +1 -1
  124. package/dist/multistream/receiveSlotManager.js +39 -36
  125. package/dist/multistream/receiveSlotManager.js.map +1 -1
  126. package/dist/multistream/remoteMedia.js +3 -1
  127. package/dist/multistream/remoteMedia.js.map +1 -1
  128. package/dist/multistream/remoteMediaGroup.js +76 -5
  129. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  130. package/dist/multistream/remoteMediaManager.js +366 -104
  131. package/dist/multistream/remoteMediaManager.js.map +1 -1
  132. package/dist/multistream/sendSlotManager.js +255 -0
  133. package/dist/multistream/sendSlotManager.js.map +1 -0
  134. package/dist/reachability/clusterReachability.js +356 -0
  135. package/dist/reachability/clusterReachability.js.map +1 -0
  136. package/dist/reachability/index.js +263 -390
  137. package/dist/reachability/index.js.map +1 -1
  138. package/dist/reachability/request.js +6 -4
  139. package/dist/reachability/request.js.map +1 -1
  140. package/dist/reachability/util.js +29 -0
  141. package/dist/reachability/util.js.map +1 -0
  142. package/dist/reconnection-manager/index.js +266 -202
  143. package/dist/reconnection-manager/index.js.map +1 -1
  144. package/dist/recording-controller/index.js +21 -2
  145. package/dist/recording-controller/index.js.map +1 -1
  146. package/dist/recording-controller/util.js +9 -8
  147. package/dist/recording-controller/util.js.map +1 -1
  148. package/dist/roap/index.js +51 -28
  149. package/dist/roap/index.js.map +1 -1
  150. package/dist/roap/request.js +48 -64
  151. package/dist/roap/request.js.map +1 -1
  152. package/dist/roap/turnDiscovery.js +220 -70
  153. package/dist/roap/turnDiscovery.js.map +1 -1
  154. package/dist/rtcMetrics/constants.js +12 -0
  155. package/dist/rtcMetrics/constants.js.map +1 -0
  156. package/dist/rtcMetrics/index.js +179 -0
  157. package/dist/rtcMetrics/index.js.map +1 -0
  158. package/dist/statsAnalyzer/index.js +357 -295
  159. package/dist/statsAnalyzer/index.js.map +1 -1
  160. package/dist/statsAnalyzer/mqaUtil.js +296 -156
  161. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  162. package/dist/types/annotation/annotation.types.d.ts +42 -0
  163. package/dist/types/annotation/constants.d.ts +31 -0
  164. package/dist/types/annotation/index.d.ts +117 -0
  165. package/dist/types/breakouts/events.d.ts +8 -0
  166. package/dist/types/breakouts/utils.d.ts +14 -0
  167. package/dist/types/common/errors/no-meeting-info.d.ts +14 -0
  168. package/dist/types/common/errors/reclaim-host-role-errors.d.ts +60 -0
  169. package/dist/types/common/errors/webex-errors.d.ts +25 -1
  170. package/dist/types/common/logs/request.d.ts +2 -0
  171. package/dist/types/common/queue.d.ts +9 -7
  172. package/dist/types/config.d.ts +2 -7
  173. package/dist/types/constants.d.ts +203 -31
  174. package/dist/types/controls-options-manager/enums.d.ts +11 -1
  175. package/dist/types/controls-options-manager/index.d.ts +17 -1
  176. package/dist/types/controls-options-manager/types.d.ts +43 -0
  177. package/dist/types/controls-options-manager/util.d.ts +1 -7
  178. package/dist/types/index.d.ts +6 -5
  179. package/dist/types/interceptors/index.d.ts +2 -0
  180. package/dist/types/interceptors/locusRetry.d.ts +27 -0
  181. package/dist/types/interpretation/collection.d.ts +5 -0
  182. package/dist/types/interpretation/index.d.ts +5 -0
  183. package/dist/types/interpretation/siLanguage.d.ts +5 -0
  184. package/dist/types/locus-info/index.d.ts +57 -4
  185. package/dist/types/locus-info/parser.d.ts +66 -6
  186. package/dist/types/media/index.d.ts +2 -0
  187. package/dist/types/media/properties.d.ts +34 -49
  188. package/dist/types/mediaQualityMetrics/config.d.ts +99 -223
  189. package/dist/types/meeting/in-meeting-actions.d.ts +86 -2
  190. package/dist/types/meeting/index.d.ts +567 -496
  191. package/dist/types/meeting/locusMediaRequest.d.ts +74 -0
  192. package/dist/types/meeting/muteState.d.ts +93 -25
  193. package/dist/types/meeting/request.d.ts +64 -43
  194. package/dist/types/meeting/util.d.ts +117 -1
  195. package/dist/types/meeting-info/index.d.ts +13 -1
  196. package/dist/types/meeting-info/meeting-info-v2.d.ts +31 -1
  197. package/dist/types/meetings/collection.d.ts +17 -0
  198. package/dist/types/meetings/index.d.ts +113 -21
  199. package/dist/types/meetings/meetings.types.d.ts +4 -0
  200. package/dist/types/member/index.d.ts +14 -0
  201. package/dist/types/member/types.d.ts +32 -0
  202. package/dist/types/members/collection.d.ts +5 -0
  203. package/dist/types/members/index.d.ts +35 -2
  204. package/dist/types/members/request.d.ts +73 -9
  205. package/dist/types/members/types.d.ts +25 -0
  206. package/dist/types/members/util.d.ts +214 -1
  207. package/dist/types/metrics/constants.d.ts +17 -0
  208. package/dist/types/metrics/index.d.ts +4 -111
  209. package/dist/types/multistream/mediaRequestManager.d.ts +72 -3
  210. package/dist/types/multistream/receiveSlot.d.ts +7 -3
  211. package/dist/types/multistream/receiveSlotManager.d.ts +14 -4
  212. package/dist/types/multistream/remoteMedia.d.ts +3 -31
  213. package/dist/types/multistream/remoteMediaGroup.d.ts +2 -9
  214. package/dist/types/multistream/remoteMediaManager.d.ts +62 -2
  215. package/dist/types/multistream/sendSlotManager.d.ts +70 -0
  216. package/dist/types/reachability/clusterReachability.d.ts +109 -0
  217. package/dist/types/reachability/index.d.ts +60 -95
  218. package/dist/types/reachability/request.d.ts +3 -1
  219. package/dist/types/reachability/util.d.ts +8 -0
  220. package/dist/types/reconnection-manager/index.d.ts +19 -0
  221. package/dist/types/recording-controller/index.d.ts +15 -1
  222. package/dist/types/recording-controller/util.d.ts +5 -4
  223. package/dist/types/roap/index.d.ts +2 -1
  224. package/dist/types/roap/request.d.ts +9 -8
  225. package/dist/types/roap/turnDiscovery.d.ts +39 -5
  226. package/dist/types/rtcMetrics/constants.d.ts +4 -0
  227. package/dist/types/rtcMetrics/index.d.ts +61 -0
  228. package/dist/types/statsAnalyzer/index.d.ts +34 -12
  229. package/dist/types/statsAnalyzer/mqaUtil.d.ts +28 -4
  230. package/dist/types/webinar/collection.d.ts +16 -0
  231. package/dist/types/webinar/index.d.ts +5 -0
  232. package/dist/webinar/collection.js +44 -0
  233. package/dist/webinar/collection.js.map +1 -0
  234. package/dist/webinar/index.js +69 -0
  235. package/dist/webinar/index.js.map +1 -0
  236. package/package.json +22 -19
  237. package/src/annotation/annotation.types.ts +50 -0
  238. package/src/annotation/constants.ts +36 -0
  239. package/src/annotation/index.ts +328 -0
  240. package/src/breakouts/README.md +35 -11
  241. package/src/breakouts/breakout.ts +67 -9
  242. package/src/breakouts/events.ts +56 -0
  243. package/src/breakouts/index.ts +558 -59
  244. package/src/breakouts/utils.ts +42 -0
  245. package/src/common/errors/no-meeting-info.ts +24 -0
  246. package/src/common/errors/reclaim-host-role-errors.ts +134 -0
  247. package/src/common/errors/webex-errors.ts +44 -2
  248. package/src/common/logs/logger-proxy.ts +1 -1
  249. package/src/common/logs/request.ts +5 -1
  250. package/src/common/queue.ts +22 -8
  251. package/src/config.ts +4 -9
  252. package/src/constants.ts +229 -21
  253. package/src/controls-options-manager/enums.ts +12 -0
  254. package/src/controls-options-manager/index.ts +116 -21
  255. package/src/controls-options-manager/types.ts +59 -0
  256. package/src/controls-options-manager/util.ts +294 -14
  257. package/src/index.ts +44 -0
  258. package/src/interceptors/index.ts +3 -0
  259. package/src/interceptors/locusRetry.ts +67 -0
  260. package/src/interpretation/README.md +60 -0
  261. package/src/interpretation/collection.ts +19 -0
  262. package/src/interpretation/index.ts +349 -0
  263. package/src/interpretation/siLanguage.ts +18 -0
  264. package/src/locus-info/controlsUtils.ts +108 -0
  265. package/src/locus-info/index.ts +417 -59
  266. package/src/locus-info/infoUtils.ts +10 -2
  267. package/src/locus-info/mediaSharesUtils.ts +80 -0
  268. package/src/locus-info/parser.ts +258 -47
  269. package/src/locus-info/selfUtils.ts +81 -5
  270. package/src/media/index.ts +100 -108
  271. package/src/media/properties.ts +88 -117
  272. package/src/mediaQualityMetrics/config.ts +103 -238
  273. package/src/meeting/in-meeting-actions.ts +171 -3
  274. package/src/meeting/index.ts +3411 -2435
  275. package/src/meeting/locusMediaRequest.ts +313 -0
  276. package/src/meeting/muteState.ts +223 -136
  277. package/src/meeting/request.ts +155 -120
  278. package/src/meeting/util.ts +685 -395
  279. package/src/meeting-info/index.ts +81 -8
  280. package/src/meeting-info/meeting-info-v2.ts +170 -14
  281. package/src/meeting-info/util.ts +1 -1
  282. package/src/meeting-info/utilv2.ts +23 -23
  283. package/src/meetings/collection.ts +33 -0
  284. package/src/meetings/index.ts +507 -127
  285. package/src/meetings/meetings.types.ts +12 -0
  286. package/src/meetings/request.ts +2 -0
  287. package/src/meetings/util.ts +81 -12
  288. package/src/member/index.ts +58 -0
  289. package/src/member/types.ts +38 -0
  290. package/src/member/util.ts +141 -25
  291. package/src/members/collection.ts +8 -0
  292. package/src/members/index.ts +134 -8
  293. package/src/members/request.ts +97 -17
  294. package/src/members/types.ts +29 -0
  295. package/src/members/util.ts +333 -240
  296. package/src/metrics/constants.ts +17 -0
  297. package/src/metrics/index.ts +1 -469
  298. package/src/multistream/mediaRequestManager.ts +271 -56
  299. package/src/multistream/receiveSlot.ts +11 -4
  300. package/src/multistream/receiveSlotManager.ts +34 -24
  301. package/src/multistream/remoteMedia.ts +5 -3
  302. package/src/multistream/remoteMediaGroup.ts +78 -0
  303. package/src/multistream/remoteMediaManager.ts +248 -44
  304. package/src/multistream/sendSlotManager.ts +199 -0
  305. package/src/reachability/clusterReachability.ts +320 -0
  306. package/src/reachability/index.ts +229 -346
  307. package/src/reachability/request.ts +8 -4
  308. package/src/reachability/util.ts +24 -0
  309. package/src/reconnection-manager/index.ts +128 -97
  310. package/src/recording-controller/index.ts +20 -3
  311. package/src/recording-controller/util.ts +26 -9
  312. package/src/roap/index.ts +52 -23
  313. package/src/roap/request.ts +48 -67
  314. package/src/roap/turnDiscovery.ts +147 -49
  315. package/src/rtcMetrics/constants.ts +3 -0
  316. package/src/rtcMetrics/index.ts +166 -0
  317. package/src/statsAnalyzer/index.ts +457 -416
  318. package/src/statsAnalyzer/mqaUtil.ts +317 -170
  319. package/src/webinar/collection.ts +31 -0
  320. package/src/webinar/index.ts +62 -0
  321. package/test/integration/spec/converged-space-meetings.js +60 -3
  322. package/test/integration/spec/journey.js +320 -261
  323. package/test/integration/spec/space-meeting.js +76 -3
  324. package/test/unit/spec/annotation/index.ts +418 -0
  325. package/test/unit/spec/breakouts/breakout.ts +118 -28
  326. package/test/unit/spec/breakouts/events.ts +89 -0
  327. package/test/unit/spec/breakouts/index.ts +1349 -114
  328. package/test/unit/spec/breakouts/utils.js +52 -1
  329. package/test/unit/spec/common/queue.js +31 -2
  330. package/test/unit/spec/controls-options-manager/index.js +163 -0
  331. package/test/unit/spec/controls-options-manager/util.js +576 -60
  332. package/test/unit/spec/fixture/locus.js +1 -0
  333. package/test/unit/spec/interceptors/locusRetry.ts +131 -0
  334. package/test/unit/spec/interpretation/collection.ts +15 -0
  335. package/test/unit/spec/interpretation/index.ts +625 -0
  336. package/test/unit/spec/interpretation/siLanguage.ts +28 -0
  337. package/test/unit/spec/locus-info/controlsUtils.js +316 -43
  338. package/test/unit/spec/locus-info/index.js +1363 -37
  339. package/test/unit/spec/locus-info/infoUtils.js +37 -15
  340. package/test/unit/spec/locus-info/lib/SeqCmp.json +16 -0
  341. package/test/unit/spec/locus-info/mediaSharesUtils.ts +41 -0
  342. package/test/unit/spec/locus-info/parser.js +116 -35
  343. package/test/unit/spec/locus-info/selfConstant.js +27 -4
  344. package/test/unit/spec/locus-info/selfUtils.js +208 -17
  345. package/test/unit/spec/media/index.ts +173 -81
  346. package/test/unit/spec/media/properties.ts +2 -2
  347. package/test/unit/spec/meeting/in-meeting-actions.ts +85 -3
  348. package/test/unit/spec/meeting/index.js +6821 -2172
  349. package/test/unit/spec/meeting/locusMediaRequest.ts +442 -0
  350. package/test/unit/spec/meeting/muteState.js +402 -212
  351. package/test/unit/spec/meeting/request.js +473 -54
  352. package/test/unit/spec/meeting/utils.js +773 -67
  353. package/test/unit/spec/meeting-info/index.js +300 -0
  354. package/test/unit/spec/meeting-info/meetinginfov2.js +526 -5
  355. package/test/unit/spec/meeting-info/utilv2.js +21 -0
  356. package/test/unit/spec/meetings/collection.js +26 -0
  357. package/test/unit/spec/meetings/index.js +1415 -213
  358. package/test/unit/spec/meetings/utils.js +229 -2
  359. package/test/unit/spec/member/index.js +61 -6
  360. package/test/unit/spec/member/util.js +510 -34
  361. package/test/unit/spec/members/index.js +432 -1
  362. package/test/unit/spec/members/request.js +206 -27
  363. package/test/unit/spec/members/utils.js +210 -0
  364. package/test/unit/spec/metrics/index.js +1 -50
  365. package/test/unit/spec/multistream/mediaRequestManager.ts +781 -114
  366. package/test/unit/spec/multistream/receiveSlot.ts +9 -1
  367. package/test/unit/spec/multistream/receiveSlotManager.ts +32 -30
  368. package/test/unit/spec/multistream/remoteMedia.ts +2 -0
  369. package/test/unit/spec/multistream/remoteMediaGroup.ts +345 -0
  370. package/test/unit/spec/multistream/remoteMediaManager.ts +525 -0
  371. package/test/unit/spec/multistream/sendSlotManager.ts +274 -0
  372. package/test/unit/spec/reachability/clusterReachability.ts +279 -0
  373. package/test/unit/spec/reachability/index.ts +551 -14
  374. package/test/unit/spec/reachability/request.js +3 -1
  375. package/test/unit/spec/reachability/util.ts +40 -0
  376. package/test/unit/spec/reconnection-manager/index.js +171 -11
  377. package/test/unit/spec/recording-controller/index.js +294 -218
  378. package/test/unit/spec/recording-controller/util.js +223 -96
  379. package/test/unit/spec/roap/index.ts +180 -83
  380. package/test/unit/spec/roap/request.ts +100 -62
  381. package/test/unit/spec/roap/turnDiscovery.ts +388 -96
  382. package/test/unit/spec/rtcMetrics/index.ts +122 -0
  383. package/test/unit/spec/stats-analyzer/index.js +1252 -12
  384. package/test/unit/spec/webinar/collection.ts +13 -0
  385. package/test/unit/spec/webinar/index.ts +60 -0
  386. package/test/utils/integrationTestUtils.js +46 -0
  387. package/test/utils/testUtils.js +0 -57
  388. package/test/utils/webex-test-users.js +12 -4
  389. package/dist/metrics/config.js +0 -289
  390. package/dist/metrics/config.js.map +0 -1
  391. package/dist/types/metrics/config.d.ts +0 -169
  392. package/src/index.js +0 -18
  393. package/src/metrics/config.ts +0 -485
@@ -3,6 +3,10 @@
3
3
  */
4
4
  import 'jsdom-global/register';
5
5
 
6
+ // Polyfill for crypto: https://github.com/jsdom/jsdom/issues/1612#issuecomment-663210638
7
+ import {Crypto} from '@peculiar/webcrypto';
8
+ global.crypto = new Crypto();
9
+
6
10
  import Device from '@webex/internal-plugin-device';
7
11
  import Mercury from '@webex/internal-plugin-mercury';
8
12
  import {assert} from '@webex/test-helper-chai';
@@ -11,14 +15,16 @@ import sinon from 'sinon';
11
15
  import uuid from 'uuid';
12
16
  import StaticConfig from '@webex/plugin-meetings/src/common/config';
13
17
  import TriggerProxy from '@webex/plugin-meetings/src/common/events/trigger-proxy';
18
+ import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
14
19
  import LoggerConfig from '@webex/plugin-meetings/src/common/logs/logger-config';
15
- import Meeting from '@webex/plugin-meetings/src/meeting';
20
+ import Meeting, {CallStateForMetrics} from '@webex/plugin-meetings/src/meeting';
16
21
  import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
17
22
  import Meetings from '@webex/plugin-meetings/src/meetings';
18
23
  import MeetingCollection from '@webex/plugin-meetings/src/meetings/collection';
19
24
  import MeetingsUtil from '@webex/plugin-meetings/src/meetings/util';
20
25
  import PersonalMeetingRoom from '@webex/plugin-meetings/src/personal-meeting-room';
21
26
  import Reachability from '@webex/plugin-meetings/src/reachability';
27
+ import Metrics from '@webex/plugin-meetings/src/metrics';
22
28
 
23
29
  import testUtils from '../../../utils/testUtils';
24
30
  import {
@@ -29,6 +35,12 @@ import {
29
35
  LOCUSINFO,
30
36
  EVENT_TRIGGERS,
31
37
  } from '../../../../src/constants';
38
+ import CaptchaError from '@webex/plugin-meetings/src/common/errors/captcha-error';
39
+ import {forEach} from 'lodash';
40
+ import PasswordError from '@webex/plugin-meetings/src/common/errors/password-error';
41
+ import PermissionError from '@webex/plugin-meetings/src/common/errors/permission';
42
+ import {NoiseReductionEffect, VirtualBackgroundEffect} from '@webex/media-helpers';
43
+ import NoMeetingInfoError from '../../../../src/common/errors/no-meeting-info';
32
44
 
33
45
  describe('plugin-meetings', () => {
34
46
  const logger = {
@@ -40,6 +52,8 @@ describe('plugin-meetings', () => {
40
52
  debug: () => {},
41
53
  };
42
54
 
55
+ let triggerProxyStub;
56
+
43
57
  beforeEach(() => {
44
58
  StaticConfig.set({
45
59
  bandwidth: {
@@ -51,7 +65,7 @@ describe('plugin-meetings', () => {
51
65
  verboseEvents: true,
52
66
  enable: false,
53
67
  });
54
- TriggerProxy.trigger = sinon.stub().returns(true);
68
+ triggerProxyStub = sinon.stub(TriggerProxy, 'trigger').returns(true);
55
69
  });
56
70
 
57
71
  let webex;
@@ -60,6 +74,7 @@ describe('plugin-meetings', () => {
60
74
  let url1;
61
75
  let test1;
62
76
  let test2;
77
+ let locusInfo;
63
78
 
64
79
  describe('meetings index', () => {
65
80
  beforeEach(() => {
@@ -69,6 +84,10 @@ describe('plugin-meetings', () => {
69
84
  uri1 = `test-${uuid.v4()}@example.com`;
70
85
  test1 = `test-${uuid.v4()}`;
71
86
  test2 = `test2-${uuid.v4()}`;
87
+ locusInfo = {
88
+ parse: sinon.stub().returns(true),
89
+ updateMainSessionLocusCache: sinon.stub(),
90
+ };
72
91
  webex = new MockWebex({
73
92
  children: {
74
93
  device: Device,
@@ -151,6 +170,10 @@ describe('plugin-meetings', () => {
151
170
  webex.emit('ready');
152
171
  });
153
172
 
173
+ afterEach(() => {
174
+ sinon.restore();
175
+ });
176
+
154
177
  it('has a webex instance with a meetings property', () => {
155
178
  assert.exists(webex, 'webex was initialized with children');
156
179
  assert.exists(webex.meetings, 'meetings child was set up on the webex instance');
@@ -217,30 +240,15 @@ describe('plugin-meetings', () => {
217
240
  });
218
241
  });
219
242
 
220
- describe('#_toggleTurnDiscovery', () => {
221
- it('should have toggleAdhocMeetings', () => {
222
- assert.equal(typeof webex.meetings._toggleTurnDiscovery, 'function');
243
+ describe('#_toggleTcpReachability', () => {
244
+ it('should have _toggleTcpReachability', () => {
245
+ assert.equal(typeof webex.meetings._toggleTcpReachability, 'function');
223
246
  });
224
247
 
225
248
  describe('success', () => {
226
- it('should update meetings to do TURN discovery', () => {
227
- webex.meetings._toggleTurnDiscovery(true);
228
- assert.equal(webex.meetings.config.experimental.enableTurnDiscovery, true);
229
-
230
- webex.meetings._toggleTurnDiscovery(false);
231
- assert.equal(webex.meetings.config.experimental.enableTurnDiscovery, false);
232
- });
233
- });
234
-
235
- describe('failure', () => {
236
- it('should not accept non boolean input', () => {
237
- const currentEnableTurnDiscovery = webex.meetings.config.experimental.enableTurnDiscovery;
238
-
239
- webex.meetings._toggleTurnDiscovery('test');
240
- assert.equal(
241
- webex.meetings.config.experimental.enableAdhocMeetings,
242
- currentEnableTurnDiscovery
243
- );
249
+ it('should update meetings to do TCP reachability', () => {
250
+ webex.meetings._toggleTcpReachability(true);
251
+ assert.equal(webex.meetings.config.experimental.enableTcpReachability, true);
244
252
  });
245
253
  });
246
254
  });
@@ -338,37 +346,110 @@ describe('plugin-meetings', () => {
338
346
  });
339
347
  });
340
348
 
349
+ describe('virtual background effect', () => {
350
+ beforeEach(() => {
351
+ webex.credentials = {
352
+ supertoken: {
353
+ access_token: 'fake_token',
354
+ },
355
+ };
356
+ });
357
+
358
+ it('creates background effect', async () => {
359
+ const result = await webex.meetings.createVirtualBackgroundEffect();
360
+
361
+ assert.exists(result);
362
+ assert.instanceOf(result, VirtualBackgroundEffect);
363
+ assert.containsAllKeys(result, ['loadModel', 'isEnabled', 'options']);
364
+ assert.deepEqual(result.options, {
365
+ mode: 'BLUR',
366
+ blurStrength: 'STRONG',
367
+ generator: 'worker',
368
+ quality: 'LOW',
369
+ authToken: 'fake_token',
370
+ mirror: false,
371
+ });
372
+ assert.exists(result.enable);
373
+ assert.exists(result.disable);
374
+ assert.exists(result.dispose);
375
+ });
376
+
377
+ it('creates background effect with custom options passed', async () => {
378
+ const effectOptions = {
379
+ generator: 'local',
380
+ frameRate: 45,
381
+ mode: 'IMAGE',
382
+ mirror: false,
383
+ quality: 'HIGH',
384
+ blurStrength: 'STRONG',
385
+ bgImageUrl: 'https://test.webex.com/landscape.5a535788.jpg',
386
+ };
387
+
388
+ const result = await webex.meetings.createVirtualBackgroundEffect(effectOptions);
389
+
390
+ assert.exists(result);
391
+ assert.instanceOf(result, VirtualBackgroundEffect);
392
+ assert.containsAllKeys(result, ['loadModel', 'isEnabled', 'options']);
393
+ assert.deepEqual(result.options, {...effectOptions, authToken: 'fake_token'});
394
+ assert.exists(result.enable);
395
+ assert.exists(result.disable);
396
+ assert.exists(result.dispose);
397
+ });
398
+ });
399
+
400
+ describe('noise reduction effect', () => {
401
+ beforeEach(() => {
402
+ webex.credentials = {
403
+ supertoken: {
404
+ access_token: 'fake_token',
405
+ },
406
+ };
407
+ });
408
+
409
+ it('creates noise reduction effect', async () => {
410
+ const result = await webex.meetings.createNoiseReductionEffect({audioContext: {}});
411
+
412
+ assert.exists(result);
413
+ assert.instanceOf(result, NoiseReductionEffect);
414
+ assert.containsAllKeys(result, ['audioContext', 'isEnabled', 'isReady', 'options']);
415
+ assert.deepEqual(result.options, {
416
+ authToken: 'fake_token',
417
+ audioContext: {},
418
+ });
419
+ assert.exists(result.enable);
420
+ assert.exists(result.disable);
421
+ assert.exists(result.dispose);
422
+ });
423
+
424
+ it('creates noise reduction effect with custom options passed', async () => {
425
+ const effectOptions = {
426
+ audioContext: {},
427
+ mode: 'WORKLET',
428
+ env: 'prod',
429
+ };
430
+
431
+ const result = await webex.meetings.createNoiseReductionEffect(effectOptions);
432
+
433
+ assert.exists(result);
434
+ assert.instanceOf(result, NoiseReductionEffect);
435
+ assert.containsAllKeys(result, ['audioContext', 'isEnabled', 'isReady', 'options']);
436
+ assert.deepEqual(result.options, {...effectOptions, authToken: 'fake_token'});
437
+ assert.exists(result.enable);
438
+ assert.exists(result.disable);
439
+ assert.exists(result.dispose);
440
+ });
441
+ });
442
+
341
443
  describe('gets', () => {
342
444
  describe('#getReachability', () => {
343
445
  it('should have #getReachability', () => {
344
446
  assert.exists(webex.meetings.getReachability);
345
447
  });
346
- describe('before #setReachability', () => {
347
- it('does not get a reachability instance', () => {
348
- const reachability = webex.meetings.getReachability();
349
-
350
- assert.notExists(
351
- reachability,
352
- 'reachability is undefined because #setReachability has not been called'
353
- );
354
- });
355
- });
356
- describe('after #setReachability', () => {
357
- beforeEach(() => {
358
- webex.meetings.setReachability();
359
- const reachabilityMocker = webex.meetings.getReachability();
360
-
361
- sinon.stub(reachabilityMocker, 'gatherReachability').returns(true);
362
- });
363
- it('gets the reachability data instance from webex.meetings', () => {
364
- const reachability = webex.meetings.getReachability();
448
+ it('gets the reachability data instance from webex.meetings', () => {
449
+ const reachability = webex.meetings.getReachability();
365
450
 
366
- assert.exists(
367
- reachability,
368
- 'reachability is defined because #setReachability has been called'
369
- );
370
- assert.instanceOf(reachability, Reachability, 'should be a reachability instance');
371
- });
451
+ assert.exists(reachability, 'reachability is defined');
452
+ assert.instanceOf(reachability, Reachability, 'should be a reachability instance');
372
453
  });
373
454
  });
374
455
  describe('#getPersonalMeetingRoom', () => {
@@ -430,6 +511,27 @@ describe('plugin-meetings', () => {
430
511
  it('should have #syncMeetings', () => {
431
512
  assert.exists(webex.meetings.syncMeetings);
432
513
  });
514
+ it('should do nothing and return a resolved promise if unverified guest', async () => {
515
+ webex.meetings.request.getActiveMeetings = sinon.stub().returns(
516
+ Promise.resolve({
517
+ loci: [
518
+ {
519
+ url: url1,
520
+ },
521
+ ],
522
+ })
523
+ );
524
+ webex.credentials.isUnverifiedGuest = true;
525
+ LoggerProxy.logger.info = sinon.stub();
526
+
527
+ await webex.meetings.syncMeetings();
528
+
529
+ assert.notCalled(webex.meetings.request.getActiveMeetings);
530
+ assert.calledWith(
531
+ LoggerProxy.logger.info,
532
+ 'Meetings:index#syncMeetings --> skipping meeting sync as unverified guest'
533
+ );
534
+ });
433
535
  describe('succesful requests', () => {
434
536
  beforeEach(() => {
435
537
  webex.meetings.request.getActiveMeetings = sinon.stub().returns(
@@ -443,21 +545,16 @@ describe('plugin-meetings', () => {
443
545
  );
444
546
  });
445
547
  describe('when meeting is returned', () => {
446
- let parse;
447
-
448
548
  beforeEach(() => {
449
- parse = sinon.stub().returns(true);
450
549
  webex.meetings.meetingCollection.getByKey = sinon.stub().returns({
451
- locusInfo: {
452
- parse,
453
- },
550
+ locusInfo,
454
551
  });
455
552
  });
456
553
  it('tests the sync meeting calls for existing meeting', async () => {
457
554
  await webex.meetings.syncMeetings();
458
555
  assert.calledOnce(webex.meetings.request.getActiveMeetings);
459
556
  assert.calledOnce(webex.meetings.meetingCollection.getByKey);
460
- assert.calledOnce(parse);
557
+ assert.calledOnce(locusInfo.parse);
461
558
  assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
462
559
  });
463
560
  });
@@ -470,6 +567,7 @@ describe('plugin-meetings', () => {
470
567
  webex.meetings.create = sinon.stub().returns(
471
568
  Promise.resolve({
472
569
  locusInfo: {
570
+ ...locusInfo,
473
571
  initialSetup,
474
572
  },
475
573
  })
@@ -478,7 +576,7 @@ describe('plugin-meetings', () => {
478
576
  it('tests the sync meeting calls for not existing meeting', async () => {
479
577
  await webex.meetings.syncMeetings();
480
578
  assert.calledOnce(webex.meetings.request.getActiveMeetings);
481
- assert.callCount(webex.meetings.meetingCollection.getByKey, 4);
579
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 5);
482
580
  assert.calledOnce(initialSetup);
483
581
  assert.calledOnce(webex.meetings.create);
484
582
  assert.calledWith(webex.meetings.request.getActiveMeetings);
@@ -495,46 +593,71 @@ describe('plugin-meetings', () => {
495
593
  });
496
594
  });
497
595
  });
498
- describe('destory non active meeting', () => {
499
- let initialSetup;
500
- let parse;
596
+ describe('when destroying meeting is needed', () => {
501
597
  let destroySpy;
502
598
 
599
+ const meetingCollectionMeetings = {
600
+ stillValidLocusMeeting: {
601
+ locusUrl: 'still-valid-locus-url',
602
+ sendCallAnalyzerMetrics: sinon.stub(),
603
+ },
604
+ noLongerValidLocusMeeting: {
605
+ locusUrl: 'no-longer-valid-locus-url',
606
+ sendCallAnalyzerMetrics: sinon.stub(),
607
+ },
608
+ otherNonLocusMeeting1: {
609
+ locusUrl: null,
610
+ sendCallAnalyzerMetrics: sinon.stub(),
611
+ },
612
+ otherNonLocusMeeting2: {
613
+ locusUrl: undefined,
614
+ sendCallAnalyzerMetrics: sinon.stub(),
615
+ },
616
+ };
617
+
503
618
  beforeEach(() => {
504
619
  destroySpy = sinon.spy(webex.meetings, 'destroy');
505
- parse = sinon.stub().returns(true);
506
- initialSetup = sinon.stub().returns(true);
507
- webex.meetings.meetingCollection.getByKey = sinon.stub().returns({
508
- locusInfo: {
509
- parse,
510
- },
511
- sendCallAnalyzerMetrics: sinon.stub(),
512
- });
513
- webex.meetings.meetingCollection.getAll = sinon.stub().returns({
514
- meetingutk: {
515
- locusUrl: 'fdfdjfdhj',
516
- sendCallAnalyzerMetrics: sinon.stub(),
517
- },
518
- });
519
- webex.meetings.create = sinon.stub().returns(
520
- Promise.resolve({
521
- locusInfo: {
522
- initialSetup,
523
- },
524
- sendCallAnalyzerMetrics: sinon.stub(),
525
- })
526
- );
620
+ webex.meetings.meetingCollection.getAll = sinon
621
+ .stub()
622
+ .returns(meetingCollectionMeetings);
527
623
  webex.meetings.request.getActiveMeetings = sinon.stub().returns(
528
624
  Promise.resolve({
529
- loci: [],
625
+ loci: [{url: 'still-valid-locus-url'}],
530
626
  })
531
627
  );
532
628
  MeetingUtil.cleanUp = sinon.stub().returns(Promise.resolve());
533
629
  });
534
- it('destroy non active meetings', async () => {
630
+
631
+ it('destroy any meeting that has no active locus url if keepOnlyLocusMeetings is not defined', async () => {
535
632
  await webex.meetings.syncMeetings();
536
633
  assert.calledOnce(webex.meetings.request.getActiveMeetings);
537
- assert.calledOnce(destroySpy);
634
+ assert.calledOnce(webex.meetings.meetingCollection.getAll);
635
+ assert.calledWith(destroySpy, meetingCollectionMeetings.noLongerValidLocusMeeting);
636
+ assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting1);
637
+ assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting2);
638
+ assert.callCount(destroySpy, 3);
639
+
640
+ assert.callCount(MeetingUtil.cleanUp, 3);
641
+ });
642
+
643
+ it('destroy any meeting that has no active locus url if keepOnlyLocusMeetings === true', async () => {
644
+ await webex.meetings.syncMeetings({keepOnlyLocusMeetings: true});
645
+ assert.calledOnce(webex.meetings.request.getActiveMeetings);
646
+ assert.calledOnce(webex.meetings.meetingCollection.getAll);
647
+ assert.calledWith(destroySpy, meetingCollectionMeetings.noLongerValidLocusMeeting);
648
+ assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting1);
649
+ assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting2);
650
+ assert.callCount(destroySpy, 3);
651
+
652
+ assert.callCount(MeetingUtil.cleanUp, 3);
653
+ });
654
+
655
+ it('destroy any LOCUS meetings that have no active locus url if keepOnlyLocusMeetings === false', async () => {
656
+ await webex.meetings.syncMeetings({keepOnlyLocusMeetings: false});
657
+ assert.calledOnce(webex.meetings.request.getActiveMeetings);
658
+ assert.calledOnce(webex.meetings.meetingCollection.getAll);
659
+ assert.calledWith(destroySpy, meetingCollectionMeetings.noLongerValidLocusMeeting);
660
+ assert.callCount(destroySpy, 1);
538
661
 
539
662
  assert.calledOnce(MeetingUtil.cleanUp);
540
663
  });
@@ -574,14 +697,139 @@ describe('plugin-meetings', () => {
574
697
  });
575
698
  });
576
699
 
700
+ const FAKE_USE_RANDOM_DELAY = true;
701
+ const correlationId = 'my-correlationId';
702
+ const callStateForMetrics = {
703
+ correlationId: 'my-correlationId2',
704
+ joinTrigger: 'my-join-trigger',
705
+ loginType: 'my-login-type',
706
+ };
707
+
708
+ it('should call setCallStateForMetrics on any pre-existing meeting', async () => {
709
+ const fakeMeeting = {setCallStateForMetrics: sinon.mock()};
710
+ webex.meetings.meetingCollection.getByKey = sinon.stub().returns(fakeMeeting);
711
+ await webex.meetings.create(
712
+ test1,
713
+ test2,
714
+ FAKE_USE_RANDOM_DELAY,
715
+ {},
716
+ correlationId,
717
+ true,
718
+ callStateForMetrics
719
+ );
720
+ assert.calledOnceWithExactly(fakeMeeting.setCallStateForMetrics, {
721
+ ...callStateForMetrics,
722
+ correlationId,
723
+ });
724
+ });
725
+
726
+ const checkCallCreateMeeting = async (createParameters, createMeetingParameters) => {
727
+ const create = webex.meetings.create(...createParameters);
728
+
729
+ assert.exists(create.then);
730
+ await create;
731
+ assert.calledOnce(webex.meetings.createMeeting);
732
+ assert.calledWith(webex.meetings.createMeeting, ...createMeetingParameters);
733
+ };
734
+
577
735
  it('calls createMeeting and returns its promise', async () => {
578
- const FAKE_USE_RANDOM_DELAY = true;
579
- const create = webex.meetings.create(test1, test2, FAKE_USE_RANDOM_DELAY);
736
+ await checkCallCreateMeeting(
737
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, correlationId, true],
738
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, {correlationId}, true]
739
+ );
740
+ });
741
+
742
+ it('calls createMeeting, pass the meeting info param and returns its promise', async () => {
743
+ const meetingInfo = {};
744
+ await checkCallCreateMeeting(
745
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, correlationId, true, undefined, meetingInfo],
746
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, {correlationId}, true, meetingInfo]
747
+ );
748
+ });
749
+
750
+ it('calls createMeeting, pass the meeting info and meetingLookupURL param and returns its promise', async () => {
751
+ const meetingInfo = {};
752
+ await checkCallCreateMeeting(
753
+ [
754
+ test1,
755
+ test2,
756
+ FAKE_USE_RANDOM_DELAY,
757
+ {},
758
+ correlationId,
759
+ true,
760
+ undefined,
761
+ meetingInfo,
762
+ 'meetingLookupURL',
763
+ ],
764
+ [
765
+ test1,
766
+ test2,
767
+ FAKE_USE_RANDOM_DELAY,
768
+ {},
769
+ {correlationId},
770
+ true,
771
+ meetingInfo,
772
+ 'meetingLookupURL',
773
+ ]
774
+ );
775
+ });
776
+
777
+ it('calls createMeeting when failOnMissingMeetinginfo is undefined and returns its promise', async () => {
778
+ await checkCallCreateMeeting(
779
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, correlationId, undefined],
780
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, {correlationId}, false]
781
+ );
782
+ });
783
+
784
+ it('calls createMeeting when failOnMissingMeetinginfo is false and returns its promise', async () => {
785
+ await checkCallCreateMeeting(
786
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, correlationId, false],
787
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, {correlationId}, false]
788
+ );
789
+ });
790
+
791
+ it('calls createMeeting with callStateForMetrics and returns its promise', async () => {
792
+ await checkCallCreateMeeting(
793
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, undefined, true, callStateForMetrics],
794
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, callStateForMetrics, true]
795
+ );
796
+ });
797
+
798
+ it('calls createMeeting with callStateForMetrics overwritten with correlationId and returns its promise', async () => {
799
+ await checkCallCreateMeeting(
800
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, correlationId, true, callStateForMetrics],
801
+ [test1, test2, FAKE_USE_RANDOM_DELAY, {}, {...callStateForMetrics, correlationId}, true]
802
+ );
803
+ });
804
+
805
+ it('calls createMeeting with extra info params and returns its promise', async () => {
806
+ const FAKE_USE_RANDOM_DELAY = false;
807
+ const correlationId = 'my-correlationId';
808
+
809
+ const FAKE_INFO_EXTRA_PARAMS = {
810
+ mtid: 'm9fe0afd8c435e892afcce9ea25b97046',
811
+ joinTXId: 'TSmrX61wNF',
812
+ };
813
+ const create = webex.meetings.create(
814
+ test1,
815
+ test2,
816
+ FAKE_USE_RANDOM_DELAY,
817
+ FAKE_INFO_EXTRA_PARAMS,
818
+ correlationId
819
+ );
580
820
 
581
821
  assert.exists(create.then);
582
822
  await create;
583
823
  assert.calledOnce(webex.meetings.createMeeting);
584
- assert.calledWith(webex.meetings.createMeeting, test1, test2, FAKE_USE_RANDOM_DELAY);
824
+ assert.calledWith(
825
+ webex.meetings.createMeeting,
826
+ test1,
827
+ test2,
828
+ FAKE_USE_RANDOM_DELAY,
829
+ FAKE_INFO_EXTRA_PARAMS,
830
+ {correlationId},
831
+ false
832
+ );
585
833
  });
586
834
 
587
835
  it('creates a new meeting when a scheduled meeting exists in the conversation', async () => {
@@ -677,45 +925,51 @@ describe('plugin-meetings', () => {
677
925
  });
678
926
  describe('#handleLocusEvent', () => {
679
927
  describe('there was a meeting', () => {
680
- let parse;
681
-
682
928
  beforeEach(() => {
683
- parse = sinon.stub().returns(true);
684
929
  webex.meetings.meetingCollection.getByKey = sinon.stub().returns({
685
- locusInfo: {
686
- parse,
687
- },
930
+ locusInfo,
688
931
  });
689
932
  });
690
- it('should parse the meeting info', () => {
933
+ it('should parse the meeting info and update main session locus cache', () => {
934
+ sinon.stub(MeetingsUtil, 'isBreakoutLocusDTO').returns(false);
691
935
  webex.meetings.handleLocusEvent({
692
936
  locusUrl: url1,
693
937
  });
694
938
  assert.calledOnce(webex.meetings.meetingCollection.getByKey);
695
939
  assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
696
- assert.calledOnce(parse);
940
+ assert.calledOnce(locusInfo.parse);
941
+ assert.calledOnce(locusInfo.updateMainSessionLocusCache);
697
942
  assert.calledWith(
698
- parse,
943
+ locusInfo.parse,
699
944
  {
700
- locusInfo: {
701
- parse,
702
- },
945
+ locusInfo,
703
946
  },
704
947
  {
705
948
  locusUrl: url1,
706
949
  }
707
950
  );
708
951
  });
952
+
953
+ it('should not update main session locus cache', () => {
954
+ sinon.stub(MeetingsUtil, 'isBreakoutLocusDTO').returns(true);
955
+ webex.meetings.handleLocusEvent({
956
+ locusUrl: url1,
957
+ });
958
+ assert.notCalled(locusInfo.updateMainSessionLocusCache);
959
+ });
709
960
  });
710
961
  describe('there was not a meeting', () => {
711
962
  let initialSetup;
963
+ const webExMeetingId = '123456';
712
964
 
713
965
  beforeEach(() => {
714
966
  initialSetup = sinon.stub().returns(true);
715
967
  webex.meetings.meetingCollection.getByKey = sinon.stub().returns(undefined);
716
968
  webex.meetings.create = sinon.stub().returns(
717
969
  Promise.resolve({
970
+ id: 'meeting-id',
718
971
  locusInfo: {
972
+ ...locusInfo,
719
973
  initialSetup,
720
974
  },
721
975
  })
@@ -735,12 +989,20 @@ describe('plugin-meetings', () => {
735
989
  callbackAddress: uri1,
736
990
  },
737
991
  },
992
+ info: {
993
+ webExMeetingId,
994
+ },
738
995
  },
739
996
  eventType: 'locus.difference',
740
997
  locusUrl: url1,
741
998
  });
742
- assert.callCount(webex.meetings.meetingCollection.getByKey, 5);
999
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 6);
743
1000
  assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
1001
+ assert.calledWith(
1002
+ webex.meetings.meetingCollection.getByKey,
1003
+ 'meetingNumber',
1004
+ webExMeetingId
1005
+ );
744
1006
  assert.calledOnce(initialSetup);
745
1007
  assert.calledWith(initialSetup, {
746
1008
  id: uuid1,
@@ -754,6 +1016,9 @@ describe('plugin-meetings', () => {
754
1016
  callbackAddress: uri1,
755
1017
  },
756
1018
  },
1019
+ info: {
1020
+ webExMeetingId,
1021
+ },
757
1022
  });
758
1023
  });
759
1024
  it('should setup the meeting by difference event without replaces', async () => {
@@ -765,12 +1030,20 @@ describe('plugin-meetings', () => {
765
1030
  callbackAddress: uri1,
766
1031
  },
767
1032
  },
1033
+ info: {
1034
+ webExMeetingId,
1035
+ },
768
1036
  },
769
1037
  eventType: 'locus.difference',
770
1038
  locusUrl: url1,
771
1039
  });
772
- assert.callCount(webex.meetings.meetingCollection.getByKey, 4);
1040
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 5);
773
1041
  assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
1042
+ assert.calledWith(
1043
+ webex.meetings.meetingCollection.getByKey,
1044
+ 'meetingNumber',
1045
+ webExMeetingId
1046
+ );
774
1047
  assert.calledOnce(initialSetup);
775
1048
  assert.calledWith(initialSetup, {
776
1049
  id: uuid1,
@@ -779,8 +1052,44 @@ describe('plugin-meetings', () => {
779
1052
  callbackAddress: uri1,
780
1053
  },
781
1054
  },
1055
+ info: {
1056
+ webExMeetingId,
1057
+ },
1058
+ });
1059
+ });
1060
+
1061
+ it('sends client event correctly on finally', async () => {
1062
+ webex.meetings.getMeetingByType = sinon.stub().returns(true);
1063
+
1064
+ await webex.meetings.handleLocusEvent({
1065
+ locus: {
1066
+ id: uuid1,
1067
+ self: {
1068
+ callBackInfo: {
1069
+ callbackAddress: uri1,
1070
+ },
1071
+ },
1072
+ info: {
1073
+ webExMeetingId,
1074
+ },
1075
+ },
1076
+ eventType: 'locus.difference',
1077
+ locusUrl: url1,
1078
+ });
1079
+
1080
+ await testUtils.flushPromises();
1081
+
1082
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
1083
+ name: 'client.call.remote-started',
1084
+ payload: {
1085
+ trigger: 'mercury-event',
1086
+ },
1087
+ options: {
1088
+ meetingId: 'meeting-id',
1089
+ },
782
1090
  });
783
1091
  });
1092
+
784
1093
  it('should setup the meeting by a not difference event', async () => {
785
1094
  await webex.meetings.handleLocusEvent({
786
1095
  locus: {
@@ -790,12 +1099,20 @@ describe('plugin-meetings', () => {
790
1099
  callbackAddress: uri1,
791
1100
  },
792
1101
  },
1102
+ info: {
1103
+ webExMeetingId,
1104
+ },
793
1105
  },
794
1106
  eventType: test1,
795
1107
  locusUrl: url1,
796
1108
  });
797
- assert.callCount(webex.meetings.meetingCollection.getByKey, 4);
1109
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 5);
798
1110
  assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
1111
+ assert.calledWith(
1112
+ webex.meetings.meetingCollection.getByKey,
1113
+ 'meetingNumber',
1114
+ webExMeetingId
1115
+ );
799
1116
  assert.calledOnce(initialSetup);
800
1117
  assert.calledWith(initialSetup, {
801
1118
  id: uuid1,
@@ -804,6 +1121,9 @@ describe('plugin-meetings', () => {
804
1121
  callbackAddress: uri1,
805
1122
  },
806
1123
  },
1124
+ info: {
1125
+ webExMeetingId,
1126
+ },
807
1127
  });
808
1128
  });
809
1129
 
@@ -826,7 +1146,7 @@ describe('plugin-meetings', () => {
826
1146
 
827
1147
  it('should not try to match USM meetings by conversation url', async () => {
828
1148
  await webex.meetings.handleLocusEvent(generateFakeLocusData(true));
829
- assert.callCount(webex.meetings.meetingCollection.getByKey, 3);
1149
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 4);
830
1150
  assert.deepEqual(webex.meetings.meetingCollection.getByKey.getCall(0).args, [
831
1151
  'locusUrl',
832
1152
  url1,
@@ -843,7 +1163,7 @@ describe('plugin-meetings', () => {
843
1163
  });
844
1164
  it('should try to match non-USM meetings by conversation url', async () => {
845
1165
  await webex.meetings.handleLocusEvent(generateFakeLocusData(false));
846
- assert.callCount(webex.meetings.meetingCollection.getByKey, 4);
1166
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 5);
847
1167
  assert.deepEqual(webex.meetings.meetingCollection.getByKey.getCall(0).args, [
848
1168
  'locusUrl',
849
1169
  url1,
@@ -874,6 +1194,10 @@ describe('plugin-meetings', () => {
874
1194
  });
875
1195
  describe('successful MeetingInfo.#fetchMeetingInfo', () => {
876
1196
  let clock, setTimeoutSpy, fakeMeetingStartTimeString, FAKE_TIME_TO_START;
1197
+ const FAKE_INFO_EXTRA_PARAMS = {
1198
+ mtid: 'm9fe0afd8c435e892afcce9ea25b97046',
1199
+ joinTXId: 'TSmrX61wNF',
1200
+ };
877
1201
 
878
1202
  beforeEach(() => {
879
1203
  clock = sinon.useFakeTimers();
@@ -903,13 +1227,37 @@ describe('plugin-meetings', () => {
903
1227
  meeting,
904
1228
  destination,
905
1229
  type,
906
- expectedMeetingData = {}
1230
+ extraParams = {},
1231
+ expectedMeetingData = {},
1232
+ sendCAevents = false,
1233
+ injectMeetingInfo = false
907
1234
  ) => {
908
- assert.calledOnce(webex.meetings.meetingInfo.fetchMeetingInfo);
1235
+ if (injectMeetingInfo) {
1236
+ assert.notCalled(webex.meetings.meetingInfo.fetchMeetingInfo);
1237
+ } else {
1238
+ assert.calledOnce(webex.meetings.meetingInfo.fetchMeetingInfo);
1239
+ }
1240
+
909
1241
  assert.calledOnce(MeetingsUtil.getMeetingAddedType);
910
1242
  assert.notCalled(setTimeoutSpy);
911
- assert.calledThrice(TriggerProxy.trigger);
912
- assert.calledWith(webex.meetings.meetingInfo.fetchMeetingInfo, destination, type);
1243
+ assert.callCount(TriggerProxy.trigger, 5);
1244
+
1245
+ if (injectMeetingInfo) {
1246
+ assert.notCalled(webex.meetings.meetingInfo.fetchMeetingInfo);
1247
+ } else {
1248
+ assert.calledWith(
1249
+ webex.meetings.meetingInfo.fetchMeetingInfo,
1250
+ destination,
1251
+ type,
1252
+ null,
1253
+ null,
1254
+ undefined,
1255
+ undefined,
1256
+ extraParams,
1257
+ {meetingId: meeting.id, sendCAevents}
1258
+ );
1259
+ }
1260
+
913
1261
  assert.calledWith(MeetingsUtil.getMeetingAddedType, 'test type');
914
1262
 
915
1263
  if (expectedMeetingData.permissionToken) {
@@ -918,6 +1266,21 @@ describe('plugin-meetings', () => {
918
1266
  if (expectedMeetingData.meetingJoinUrl) {
919
1267
  assert.equal(meeting.meetingJoinUrl, expectedMeetingData.meetingJoinUrl);
920
1268
  }
1269
+ if (expectedMeetingData.correlationId) {
1270
+ assert.equal(meeting.correlationId, expectedMeetingData.correlationId);
1271
+ }
1272
+ if (expectedMeetingData.callStateForMetrics) {
1273
+ assert.deepEqual(
1274
+ meeting.callStateForMetrics,
1275
+ expectedMeetingData.callStateForMetrics
1276
+ );
1277
+ }
1278
+ if (expectedMeetingData.meetingLookupUrl) {
1279
+ assert.equal(
1280
+ meeting.meetingInfo.meetingLookupUrl,
1281
+ expectedMeetingData.meetingLookupUrl
1282
+ );
1283
+ }
921
1284
  assert.equal(meeting.destination, destination);
922
1285
  assert.equal(meeting.destinationType, type);
923
1286
  assert.calledWith(
@@ -947,107 +1310,207 @@ describe('plugin-meetings', () => {
947
1310
  const expectedMeetingData = {
948
1311
  permissionToken: 'PT',
949
1312
  meetingJoinUrl: 'meetingJoinUrl',
1313
+ correlationId: meeting.id,
950
1314
  };
951
1315
 
952
- checkCreateWithoutDelay(meeting, 'test destination', 'test type', expectedMeetingData);
1316
+ checkCreateWithoutDelay(
1317
+ meeting,
1318
+ 'test destination',
1319
+ 'test type',
1320
+ {},
1321
+ expectedMeetingData
1322
+ );
953
1323
  });
954
1324
 
955
- it('creates the meeting from a successful meeting info fetch meeting resolve testing', async () => {
956
- const meeting = await webex.meetings.createMeeting('test destination', 'test type');
957
- const expectedMeetingData = {
1325
+ it('accepts injected meeting info', async () => {
1326
+ const meetingInfo = {
958
1327
  permissionToken: 'PT',
959
1328
  meetingJoinUrl: 'meetingJoinUrl',
960
1329
  };
961
1330
 
962
- assert.instanceOf(
1331
+ const meeting = await webex.meetings.createMeeting(
1332
+ 'test destination',
1333
+ 'test type',
1334
+ false,
1335
+ {},
1336
+ undefined,
1337
+ false,
1338
+ meetingInfo
1339
+ );
1340
+
1341
+ const expectedMeetingData = {
1342
+ ...meetingInfo,
1343
+ correlationId: meeting.id,
1344
+ };
1345
+
1346
+ checkCreateWithoutDelay(
963
1347
  meeting,
964
- Meeting,
965
- 'createMeeting should eventually resolve to a Meeting Object'
1348
+ 'test destination',
1349
+ 'test type',
1350
+ {},
1351
+ expectedMeetingData,
1352
+ false,
1353
+ true
966
1354
  );
967
- checkCreateWithoutDelay(meeting, 'test destination', 'test type', expectedMeetingData);
968
1355
  });
969
1356
 
970
- it('creates the meeting from a successful meeting info fetch with random delay', async () => {
971
- const FAKE_LOCUS_MEETING = {
972
- conversationUrl: 'locusConvURL',
973
- url: 'locusUrl',
974
- info: {
975
- webExMeetingId: 'locusMeetingId',
976
- sipUri: 'locusSipUri',
977
- owner: 'locusOwner',
978
- },
979
- meeting: {
980
- startTime: fakeMeetingStartTimeString,
981
- },
982
- fullState: {
983
- active: false,
984
- },
1357
+ it('accepts injected meeting info with meeting lookup url', async () => {
1358
+ const meetingInfo = {
1359
+ permissionToken: 'PT',
1360
+ meetingJoinUrl: 'meetingJoinUrl',
985
1361
  };
986
1362
 
987
1363
  const meeting = await webex.meetings.createMeeting(
988
- FAKE_LOCUS_MEETING,
1364
+ 'test destination',
989
1365
  'test type',
990
- true
1366
+ false,
1367
+ {},
1368
+ undefined,
1369
+ false,
1370
+ meetingInfo,
1371
+ 'meetingLookupUrl'
991
1372
  );
992
1373
 
993
- assert.instanceOf(
994
- meeting,
995
- Meeting,
996
- 'createMeeting should eventually resolve to a Meeting Object'
997
- );
998
- assert.notCalled(webex.meetings.meetingInfo.fetchMeetingInfo);
999
- assert.calledOnce(setTimeoutSpy);
1000
-
1001
- // Parse meeting info with locus object
1002
- assert.equal(meeting.conversationUrl, 'locusConvURL');
1003
- assert.equal(meeting.locusUrl, 'locusUrl');
1004
- assert.equal(meeting.sipUri, 'locusSipUri');
1005
- assert.equal(meeting.meetingNumber, 'locusMeetingId');
1006
- assert.isUndefined(meeting.meetingJoinUrl);
1007
- assert.equal(meeting.owner, 'locusOwner');
1008
- assert.isUndefined(meeting.permissionToken);
1009
-
1010
- // Add meeting and send trigger
1011
- assert.calledWith(MeetingsUtil.getMeetingAddedType, 'test type');
1012
- assert.calledTwice(TriggerProxy.trigger);
1013
- assert.calledWith(
1014
- TriggerProxy.trigger,
1015
- sinon.match.instanceOf(Meetings),
1016
- {
1017
- file: 'meetings',
1018
- function: 'createMeeting',
1019
- },
1020
- 'meeting:added',
1021
- {
1022
- meeting: sinon.match.instanceOf(Meeting),
1023
- type: 'test meeting added type',
1024
- }
1025
- );
1374
+ const expectedMeetingData = {
1375
+ ...meetingInfo,
1376
+ meetingLookupUrl: 'meetingLookupUrl',
1377
+ correlationId: meeting.id,
1378
+ };
1026
1379
 
1027
- // When timer expires
1028
- clock.tick(FAKE_TIME_TO_START);
1029
- assert.calledWith(
1030
- webex.meetings.meetingInfo.fetchMeetingInfo,
1031
- FAKE_LOCUS_MEETING,
1032
- 'test type'
1380
+ checkCreateWithoutDelay(
1381
+ meeting,
1382
+ 'test destination',
1383
+ 'test type',
1384
+ {},
1385
+ expectedMeetingData,
1386
+ false,
1387
+ true
1033
1388
  );
1389
+ });
1034
1390
 
1035
- // Parse meeting info is called again with new meeting info
1036
- await testUtils.flushPromises();
1037
- assert.equal(meeting.conversationUrl, 'locusConvURL');
1038
- assert.equal(meeting.locusUrl, 'locusUrl');
1039
- assert.equal(meeting.sipUri, 'locusSipUri');
1040
- assert.equal(meeting.meetingNumber, 'locusMeetingId');
1041
- assert.equal(meeting.meetingJoinUrl, 'meetingJoinUrl');
1042
- assert.equal(meeting.owner, 'locusOwner');
1043
- assert.equal(meeting.permissionToken, 'PT');
1391
+ [undefined, FAKE_INFO_EXTRA_PARAMS].forEach((infoExtraParams) => {
1392
+ const infoExtraParamsProvided = infoExtraParams !== undefined;
1393
+
1394
+ it(`creates the meeting from a successful meeting info fetch meeting resolve testing${
1395
+ infoExtraParamsProvided ? ' with infoExtraParams' : ''
1396
+ }`, async () => {
1397
+ const meeting = await webex.meetings.createMeeting(
1398
+ 'test destination',
1399
+ 'test type',
1400
+ false,
1401
+ infoExtraParams
1402
+ );
1403
+ const expectedMeetingData = {
1404
+ permissionToken: 'PT',
1405
+ meetingJoinUrl: 'meetingJoinUrl',
1406
+ };
1407
+
1408
+ assert.instanceOf(
1409
+ meeting,
1410
+ Meeting,
1411
+ 'createMeeting should eventually resolve to a Meeting Object'
1412
+ );
1413
+ checkCreateWithoutDelay(
1414
+ meeting,
1415
+ 'test destination',
1416
+ 'test type',
1417
+ infoExtraParamsProvided ? infoExtraParams : {},
1418
+ expectedMeetingData
1419
+ );
1420
+ });
1044
1421
 
1045
- assert.calledWith(
1046
- TriggerProxy.trigger,
1047
- meeting,
1048
- {file: 'meetings', function: 'fetchMeetingInfo'},
1049
- 'meeting:meetingInfoAvailable'
1050
- );
1422
+ it(`creates the meeting from a successful meeting info fetch with random delay${
1423
+ infoExtraParamsProvided ? ' with infoExtraParams' : ''
1424
+ }`, async () => {
1425
+ const FAKE_LOCUS_MEETING = {
1426
+ conversationUrl: 'locusConvURL',
1427
+ url: 'locusUrl',
1428
+ info: {
1429
+ webExMeetingId: 'locusMeetingId',
1430
+ sipUri: 'locusSipUri',
1431
+ owner: 'locusOwner',
1432
+ },
1433
+ meeting: {
1434
+ startTime: fakeMeetingStartTimeString,
1435
+ },
1436
+ fullState: {
1437
+ active: false,
1438
+ },
1439
+ };
1440
+
1441
+ const meeting = await webex.meetings.createMeeting(
1442
+ FAKE_LOCUS_MEETING,
1443
+ 'test type',
1444
+ true,
1445
+ infoExtraParams
1446
+ );
1447
+
1448
+ assert.instanceOf(
1449
+ meeting,
1450
+ Meeting,
1451
+ 'createMeeting should eventually resolve to a Meeting Object'
1452
+ );
1453
+ assert.notCalled(webex.meetings.meetingInfo.fetchMeetingInfo);
1454
+ assert.calledOnce(setTimeoutSpy);
1455
+
1456
+ // Parse meeting info with locus object
1457
+ assert.equal(meeting.conversationUrl, 'locusConvURL');
1458
+ assert.equal(meeting.locusUrl, 'locusUrl');
1459
+ assert.equal(meeting.sipUri, 'locusSipUri');
1460
+ assert.equal(meeting.meetingNumber, 'locusMeetingId');
1461
+ assert.isUndefined(meeting.meetingJoinUrl);
1462
+ assert.equal(meeting.owner, 'locusOwner');
1463
+ assert.isUndefined(meeting.permissionToken);
1464
+
1465
+ // Add meeting and send trigger
1466
+ assert.calledWith(MeetingsUtil.getMeetingAddedType, 'test type');
1467
+ assert.calledTwice(TriggerProxy.trigger);
1468
+ assert.calledWith(
1469
+ TriggerProxy.trigger,
1470
+ sinon.match.instanceOf(Meetings),
1471
+ {
1472
+ file: 'meetings',
1473
+ function: 'createMeeting',
1474
+ },
1475
+ 'meeting:added',
1476
+ {
1477
+ meeting: sinon.match.instanceOf(Meeting),
1478
+ type: 'test meeting added type',
1479
+ }
1480
+ );
1481
+
1482
+ // When timer expires
1483
+ clock.tick(FAKE_TIME_TO_START);
1484
+ await testUtils.flushPromises();
1485
+
1486
+ assert.calledWith(
1487
+ webex.meetings.meetingInfo.fetchMeetingInfo,
1488
+ FAKE_LOCUS_MEETING,
1489
+ 'test type',
1490
+ null,
1491
+ null,
1492
+ undefined,
1493
+ undefined,
1494
+ infoExtraParamsProvided ? infoExtraParams : {}
1495
+ );
1496
+
1497
+ // Parse meeting info is called again with new meeting info
1498
+ await testUtils.flushPromises();
1499
+ assert.equal(meeting.conversationUrl, 'locusConvURL');
1500
+ assert.equal(meeting.locusUrl, 'locusUrl');
1501
+ assert.equal(meeting.sipUri, 'locusSipUri');
1502
+ assert.equal(meeting.meetingNumber, 'locusMeetingId');
1503
+ assert.equal(meeting.meetingJoinUrl, 'meetingJoinUrl');
1504
+ assert.equal(meeting.owner, 'locusOwner');
1505
+ assert.equal(meeting.permissionToken, 'PT');
1506
+
1507
+ assert.calledWith(
1508
+ TriggerProxy.trigger,
1509
+ meeting,
1510
+ {file: 'meetings', function: 'fetchMeetingInfo'},
1511
+ 'meeting:meetingInfoAvailable'
1512
+ );
1513
+ });
1051
1514
  });
1052
1515
 
1053
1516
  it('creates the meeting from a successful meeting info fetch that has no random delay because it is active', async () => {
@@ -1147,6 +1610,61 @@ describe('plugin-meetings', () => {
1147
1610
  );
1148
1611
  checkCreateWithoutDelay(meeting, FAKE_LOCUS_MEETING, 'test type');
1149
1612
  });
1613
+
1614
+ it('creates meeting with the correlationId provided', async () => {
1615
+ const meeting = await webex.meetings.createMeeting(
1616
+ 'test destination',
1617
+ 'test type',
1618
+ false,
1619
+ {},
1620
+ {correlationId: 'my-correlationId'}
1621
+ );
1622
+
1623
+ const expectedMeetingData = {
1624
+ correlationId: 'my-correlationId',
1625
+ };
1626
+
1627
+ checkCreateWithoutDelay(
1628
+ meeting,
1629
+ 'test destination',
1630
+ 'test type',
1631
+ {},
1632
+ expectedMeetingData,
1633
+ true
1634
+ );
1635
+ });
1636
+
1637
+ it('creates meeting with the callStateForMetrics provided', async () => {
1638
+ const meeting = await webex.meetings.createMeeting(
1639
+ 'test destination',
1640
+ 'test type',
1641
+ false,
1642
+ {},
1643
+ {
1644
+ correlationId: 'my-correlationId',
1645
+ joinTrigger: 'my-join-trigger',
1646
+ loginType: 'my-login-type',
1647
+ }
1648
+ );
1649
+
1650
+ const expectedMeetingData = {
1651
+ correlationId: 'my-correlationId',
1652
+ callStateForMetrics: {
1653
+ correlationId: 'my-correlationId',
1654
+ joinTrigger: 'my-join-trigger',
1655
+ loginType: 'my-login-type',
1656
+ },
1657
+ };
1658
+
1659
+ checkCreateWithoutDelay(
1660
+ meeting,
1661
+ 'test destination',
1662
+ 'test type',
1663
+ {},
1664
+ expectedMeetingData,
1665
+ true
1666
+ );
1667
+ });
1150
1668
  });
1151
1669
 
1152
1670
  describe('rejected MeetingInfo.#fetchMeetingInfo', () => {
@@ -1156,38 +1674,130 @@ describe('plugin-meetings', () => {
1156
1674
  webex.meetings.meetingInfo.fetchMeetingInfo = sinon
1157
1675
  .stub()
1158
1676
  .returns(Promise.reject(new Error('test')));
1677
+ webex.meetings.destroy = sinon.stub().returns(Promise.resolve());
1678
+ webex.meetings.createMeeting = sinon.spy(webex.meetings.createMeeting);
1159
1679
  });
1680
+
1681
+ const checkCreateMeetingWithNoMeetingInfo = async (failOnMissingMeetingInfo, destroy) => {
1682
+ try {
1683
+ const meeting = await webex.meetings.createMeeting(
1684
+ 'test destination',
1685
+ 'test type',
1686
+ undefined,
1687
+ undefined,
1688
+ undefined,
1689
+ failOnMissingMeetingInfo
1690
+ );
1691
+
1692
+ assert.instanceOf(
1693
+ meeting,
1694
+ Meeting,
1695
+ 'createMeeting should eventually resolve to a Meeting Object'
1696
+ );
1697
+ assert.calledOnce(webex.meetings.meetingInfo.fetchMeetingInfo);
1698
+ assert.calledOnce(MeetingsUtil.getMeetingAddedType);
1699
+ assert.calledThrice(TriggerProxy.trigger);
1700
+ assert.calledWith(
1701
+ webex.meetings.meetingInfo.fetchMeetingInfo,
1702
+ 'test destination',
1703
+ 'test type'
1704
+ );
1705
+
1706
+ if (destroy) {
1707
+ assert.calledWith(
1708
+ webex.meetings.destroy,
1709
+ sinon.match.instanceOf(Meeting),
1710
+ 'MISSING_MEETING_INFO'
1711
+ );
1712
+ assert.notCalled(MeetingsUtil.getMeetingAddedType);
1713
+ assert.notCalled(TriggerProxy.trigger);
1714
+ assert.throw(webex.meetings.createMeeting, 'meeting information not found');
1715
+ } else {
1716
+ assert.notCalled(webex.meetings.destroy);
1717
+ assert.calledWith(MeetingsUtil.getMeetingAddedType, 'test type');
1718
+ assert.calledWith(
1719
+ TriggerProxy.trigger,
1720
+ sinon.match.instanceOf(Meetings),
1721
+ {
1722
+ file: 'meetings',
1723
+ function: 'createMeeting',
1724
+ },
1725
+ 'meeting:added',
1726
+ {
1727
+ meeting: sinon.match.instanceOf(Meeting),
1728
+ type: 'test meeting added type',
1729
+ }
1730
+ );
1731
+ }
1732
+ } catch (err) {
1733
+ assert.instanceOf(err, NoMeetingInfoError);
1734
+ }
1735
+ };
1736
+
1160
1737
  it('creates the meeting from a rejected meeting info fetch', async () => {
1161
- const meeting = await webex.meetings.createMeeting('test destination', 'test type');
1738
+ checkCreateMeetingWithNoMeetingInfo(false, false);
1739
+ });
1162
1740
 
1163
- assert.instanceOf(
1164
- meeting,
1165
- Meeting,
1166
- 'createMeeting should eventually resolve to a Meeting Object'
1167
- );
1168
- assert.calledOnce(webex.meetings.meetingInfo.fetchMeetingInfo);
1169
- assert.calledOnce(MeetingsUtil.getMeetingAddedType);
1170
- assert.calledTwice(TriggerProxy.trigger);
1171
- assert.calledWith(
1172
- webex.meetings.meetingInfo.fetchMeetingInfo,
1173
- 'test destination',
1174
- 'test type'
1175
- );
1176
- assert.calledWith(MeetingsUtil.getMeetingAddedType, 'test type');
1177
- assert.calledWith(
1178
- TriggerProxy.trigger,
1179
- sinon.match.instanceOf(Meetings),
1741
+ it('creates the meeting from a rejected meeting info fetch and destroys it if failOnMissingMeetingInfo', async () => {
1742
+ checkCreateMeetingWithNoMeetingInfo(true, true);
1743
+ });
1744
+ });
1745
+
1746
+ describe('rejected MeetingInfo.#fetchMeetingInfo - does not log for known Error types', () => {
1747
+ forEach(
1748
+ [
1180
1749
  {
1181
- file: 'meetings',
1182
- function: 'createMeeting',
1750
+ error: new CaptchaError(),
1751
+ debugLogMessage:
1752
+ 'Meetings:index#createMeeting --> Debug CaptchaError: Captcha is required. fetching /meetingInfo for creation.',
1183
1753
  },
1184
- 'meeting:added',
1185
1754
  {
1186
- meeting: sinon.match.instanceOf(Meeting),
1187
- type: 'test meeting added type',
1188
- }
1189
- );
1190
- });
1755
+ error: new PasswordError(),
1756
+ debugLogMessage:
1757
+ 'Meetings:index#createMeeting --> Debug PasswordError: Password is required, please use verifyPassword() fetching /meetingInfo for creation.',
1758
+ },
1759
+ {
1760
+ error: new PermissionError(),
1761
+ debugLogMessage:
1762
+ 'Meetings:index#createMeeting --> Debug PermissionError: Not allowed to execute the function, some properties on server, or local client state do not allow you to complete this action. fetching /meetingInfo for creation.',
1763
+ },
1764
+ {
1765
+ error: new Error(),
1766
+ infoLogMessage: true,
1767
+ debugLogMessage:
1768
+ 'Meetings:index#createMeeting --> Debug Error fetching /meetingInfo for creation.',
1769
+ },
1770
+ ],
1771
+ ({error, debugLogMessage, infoLogMessage}) => {
1772
+ it('creates the meeting from a rejected meeting info fetch', async () => {
1773
+ webex.meetings.meetingInfo.fetchMeetingInfo = sinon
1774
+ .stub()
1775
+ .returns(Promise.reject(error));
1776
+
1777
+ LoggerProxy.logger.debug = sinon.stub();
1778
+ LoggerProxy.logger.info = sinon.stub();
1779
+
1780
+ const meeting = await webex.meetings.createMeeting('test destination', 'test type');
1781
+
1782
+ assert.instanceOf(
1783
+ meeting,
1784
+ Meeting,
1785
+ 'createMeeting should eventually resolve to a Meeting Object'
1786
+ );
1787
+
1788
+ assert.calledWith(LoggerProxy.logger.debug, debugLogMessage);
1789
+
1790
+ if (infoLogMessage) {
1791
+ assert.calledWith(
1792
+ LoggerProxy.logger.info,
1793
+ 'Meetings:index#createMeeting --> Info Unable to fetch meeting info for test destination.'
1794
+ );
1795
+ } else {
1796
+ assert.notCalled(LoggerProxy.logger.info);
1797
+ }
1798
+ });
1799
+ }
1800
+ );
1191
1801
  });
1192
1802
  });
1193
1803
  });
@@ -1267,6 +1877,8 @@ describe('plugin-meetings', () => {
1267
1877
  });
1268
1878
 
1269
1879
  describe('#fetchUserPreferredWebexSite', () => {
1880
+ let loggerProxySpy;
1881
+
1270
1882
  it('should call request.getMeetingPreferences to get the preferred webex site ', async () => {
1271
1883
  assert.isDefined(webex.meetings.preferredWebexSite);
1272
1884
  await webex.meetings.fetchUserPreferredWebexSite();
@@ -1274,7 +1886,22 @@ describe('plugin-meetings', () => {
1274
1886
  assert.equal(webex.meetings.preferredWebexSite, 'go.webex.com');
1275
1887
  });
1276
1888
 
1889
+ const setup = ({user} = {}) => {
1890
+ loggerProxySpy = sinon.spy(LoggerProxy.logger, 'error');
1891
+
1892
+ Object.assign(webex.internal, {
1893
+ services: {
1894
+ getMeetingPreferences: sinon.stub().returns(Promise.resolve({})),
1895
+ },
1896
+ user: {
1897
+ get: sinon.stub().returns(Promise.resolve(user)),
1898
+ },
1899
+ });
1900
+ };
1901
+
1277
1902
  it('should not fail if UserPreferred info is not fetched ', async () => {
1903
+ setup();
1904
+
1278
1905
  Object.assign(webex.internal, {
1279
1906
  services: {
1280
1907
  getMeetingPreferences: sinon.stub().returns(Promise.resolve({})),
@@ -1284,6 +1911,63 @@ describe('plugin-meetings', () => {
1284
1911
  await webex.meetings.fetchUserPreferredWebexSite().then(() => {
1285
1912
  assert.equal(webex.meetings.preferredWebexSite, '');
1286
1913
  });
1914
+ assert.calledOnceWithExactly(
1915
+ loggerProxySpy,
1916
+ 'Failed to fetch preferred site from user - no site will be set'
1917
+ );
1918
+ });
1919
+
1920
+ it('should fall back to fetching the site from the user', async () => {
1921
+ setup({
1922
+ user: {
1923
+ userPreferences: {
1924
+ userPreferencesItems: {
1925
+ preferredWebExSite: 'site.webex.com',
1926
+ },
1927
+ },
1928
+ },
1929
+ });
1930
+
1931
+ await webex.meetings.fetchUserPreferredWebexSite();
1932
+
1933
+ assert.equal(webex.meetings.preferredWebexSite, 'site.webex.com');
1934
+ assert.notCalled(loggerProxySpy);
1935
+ });
1936
+
1937
+ forEach(
1938
+ [
1939
+ {user: undefined},
1940
+ {user: {userPreferences: {}}},
1941
+ {user: {userPreferences: {userPreferencesItems: {}}}},
1942
+ {user: {userPreferences: {userPreferencesItems: {preferredWebExSite: undefined}}}},
1943
+ ],
1944
+ ({user}) => {
1945
+ it(`should handle invalid user data ${user}`, async () => {
1946
+ setup({user});
1947
+
1948
+ await webex.meetings.fetchUserPreferredWebexSite();
1949
+
1950
+ assert.equal(webex.meetings.preferredWebexSite, '');
1951
+ assert.calledOnceWithExactly(
1952
+ loggerProxySpy,
1953
+ 'Failed to fetch preferred site from user - no site will be set'
1954
+ );
1955
+ });
1956
+ }
1957
+ );
1958
+
1959
+ it('should handle a get user failure', async () => {
1960
+ setup();
1961
+
1962
+ webex.internal.user.get.rejects(new Error());
1963
+
1964
+ await webex.meetings.fetchUserPreferredWebexSite();
1965
+
1966
+ assert.equal(webex.meetings.preferredWebexSite, '');
1967
+ assert.calledOnceWithExactly(
1968
+ loggerProxySpy,
1969
+ 'Failed to fetch preferred site from user - no site will be set'
1970
+ );
1287
1971
  });
1288
1972
  });
1289
1973
  });
@@ -1368,5 +2052,523 @@ describe('plugin-meetings', () => {
1368
2052
  );
1369
2053
  });
1370
2054
  });
2055
+
2056
+ describe('#isNeedHandleMainLocus', () => {
2057
+ let meeting;
2058
+ let newLocus;
2059
+ beforeEach(() => {
2060
+ meeting = {
2061
+ controls: {},
2062
+ self: {},
2063
+ };
2064
+ newLocus = {
2065
+ controls: {},
2066
+ self: {},
2067
+ };
2068
+ });
2069
+ afterEach(() => {
2070
+ sinon.restore();
2071
+ });
2072
+ it('check normal case will return true', () => {
2073
+ sinon.stub(webex.meetings.meetingCollection, 'getActiveBreakoutLocus').returns(null);
2074
+ LoggerProxy.logger.log = sinon.stub();
2075
+ const result = webex.meetings.isNeedHandleMainLocus(meeting, newLocus);
2076
+ assert.equal(result, true);
2077
+ assert.calledWith(
2078
+ LoggerProxy.logger.log,
2079
+ 'Meetings:index#isNeedHandleMainLocus --> this is a normal main session locusDTO update case'
2080
+ );
2081
+ });
2082
+
2083
+ it('check self joined and joined on this device, return true', () => {
2084
+ sinon.stub(webex.meetings.meetingCollection, 'getActiveBreakoutLocus').returns(null);
2085
+ newLocus.self.state = 'JOINED';
2086
+ sinon.stub(MeetingsUtil, 'joinedOnThisDevice').returns(true);
2087
+
2088
+ LoggerProxy.logger.log = sinon.stub();
2089
+ const result = webex.meetings.isNeedHandleMainLocus(meeting, newLocus);
2090
+ assert.equal(result, true);
2091
+ assert.calledWith(
2092
+ LoggerProxy.logger.log,
2093
+ 'Meetings:index#isNeedHandleMainLocus --> self this device shown as JOINED in the main session'
2094
+ );
2095
+ });
2096
+
2097
+ it('if newLocus replaceAt time is expired, then return false', () => {
2098
+ sinon.stub(webex.meetings.meetingCollection, 'getActiveBreakoutLocus').returns({
2099
+ joinedWith: {
2100
+ replaces: [
2101
+ {
2102
+ replaceAt: '2023-03-27T02:17:02.506Z',
2103
+ },
2104
+ ],
2105
+ },
2106
+ });
2107
+ newLocus.self.state = 'JOINED';
2108
+ sinon.stub(MeetingsUtil, 'joinedOnThisDevice').returns(true);
2109
+ sinon.stub(MeetingsUtil, 'getThisDevice').returns({
2110
+ replaces: [
2111
+ {
2112
+ replaceAt: '2023-03-27T02:17:01.506Z',
2113
+ },
2114
+ ],
2115
+ });
2116
+
2117
+ LoggerProxy.logger.log = sinon.stub();
2118
+ const result = webex.meetings.isNeedHandleMainLocus(meeting, newLocus);
2119
+ assert.equal(result, false);
2120
+ assert.calledWith(
2121
+ LoggerProxy.logger.log,
2122
+ `Meetings:index#isNeedHandleMainLocus --> this is expired main joined status locus_dto replacedAt 2023-03-27T02:17:01.506Z bo replacedAt 2023-03-27T02:17:02.506Z`
2123
+ );
2124
+ });
2125
+
2126
+ it('check current is in breakout join with this device, return false', () => {
2127
+ sinon.stub(webex.meetings.meetingCollection, 'getActiveBreakoutLocus').returns({
2128
+ joinedWith: {
2129
+ correlationId: '111',
2130
+ },
2131
+ });
2132
+ newLocus.controls.breakout = {url: 'url'};
2133
+ meeting.correlationId = '111';
2134
+
2135
+ LoggerProxy.logger.log = sinon.stub();
2136
+ const result = webex.meetings.isNeedHandleMainLocus(meeting, newLocus);
2137
+ assert.equal(result, false);
2138
+ assert.calledWith(
2139
+ LoggerProxy.logger.log,
2140
+ `Meetings:index#isNeedHandleMainLocus --> there is active breakout session and joined on this device, and don't need to handle main session: url`
2141
+ );
2142
+ });
2143
+
2144
+ it('check self is moved and removed, return false', () => {
2145
+ webex.meetings.meetingCollection.getActiveBreakoutLocus = sinon.stub().returns(null);
2146
+ newLocus.self.state = 'LEFT';
2147
+ newLocus.self.reason = 'MOVED';
2148
+ newLocus.self.removed = true;
2149
+ LoggerProxy.logger.log = sinon.stub();
2150
+ const result = webex.meetings.isNeedHandleMainLocus(meeting, newLocus);
2151
+ assert.equal(result, false);
2152
+ assert.calledWith(
2153
+ LoggerProxy.logger.log,
2154
+ 'Meetings:index#isNeedHandleMainLocus --> self moved main locus with self removed status or with device resource moved, not need to handle'
2155
+ );
2156
+ });
2157
+
2158
+ it('check self is moved and device resource removed, return false', () => {
2159
+ webex.meetings.meetingCollection.getActiveBreakoutLocus = sinon.stub().returns(null);
2160
+ newLocus.self.state = 'LEFT';
2161
+ newLocus.self.reason = 'MOVED';
2162
+ sinon.stub(MeetingsUtil, 'getThisDevice').returns({
2163
+ state: 'LEFT',
2164
+ reason: 'MOVED',
2165
+ });
2166
+ LoggerProxy.logger.log = sinon.stub();
2167
+ const result = webex.meetings.isNeedHandleMainLocus(meeting, newLocus);
2168
+ assert.equal(result, false);
2169
+ assert.calledWith(
2170
+ LoggerProxy.logger.log,
2171
+ 'Meetings:index#isNeedHandleMainLocus --> self moved main locus with self removed status or with device resource moved, not need to handle'
2172
+ );
2173
+ });
2174
+
2175
+ it('check self is joined but device resource removed, return false', () => {
2176
+ webex.meetings.meetingCollection.getActiveBreakoutLocus = sinon.stub().returns(null);
2177
+ sinon.stub(MeetingsUtil, 'joinedOnThisDevice').returns(false);
2178
+ newLocus.self.state = 'JOINED';
2179
+ sinon.stub(MeetingsUtil, 'getThisDevice').returns({
2180
+ state: 'LEFT',
2181
+ reason: 'MOVED',
2182
+ });
2183
+ LoggerProxy.logger.log = sinon.stub();
2184
+ const result = webex.meetings.isNeedHandleMainLocus(meeting, newLocus);
2185
+ assert.equal(result, false);
2186
+ assert.calledWith(
2187
+ LoggerProxy.logger.log,
2188
+ 'Meetings:index#isNeedHandleMainLocus --> self device left&moved in main locus with self joined status, not need to handle'
2189
+ );
2190
+ });
2191
+ });
2192
+
2193
+ describe('#isNeedHandleLocusDTO', () => {
2194
+ let meeting;
2195
+ let newLocus;
2196
+ beforeEach(() => {
2197
+ meeting = {
2198
+ controls: {},
2199
+ self: {},
2200
+ };
2201
+ newLocus = {
2202
+ controls: {},
2203
+ self: {},
2204
+ };
2205
+ });
2206
+ afterEach(() => {
2207
+ sinon.restore();
2208
+ });
2209
+ it('initial DTO , joined breakout session, return true', () => {
2210
+ newLocus.controls.breakout = {
2211
+ sessionType: 'BREAKOUT',
2212
+ };
2213
+ newLocus.self.state = 'JOINED';
2214
+ newLocus.fullState = {
2215
+ active: true,
2216
+ };
2217
+ LoggerProxy.logger.log = sinon.stub();
2218
+ const result = webex.meetings.isNeedHandleLocusDTO(null, newLocus);
2219
+ assert.equal(result, true);
2220
+ assert.calledWith(
2221
+ LoggerProxy.logger.log,
2222
+ `Meetings:index#isNeedHandleLocusDTO --> the first breakout session locusDTO active status: true`
2223
+ );
2224
+ });
2225
+ it('others go to check isNeedHandleMainLocus', () => {
2226
+ newLocus.controls.breakout = {
2227
+ sessionType: 'MAIN',
2228
+ };
2229
+ newLocus.self.state = 'JOINED';
2230
+
2231
+ LoggerProxy.logger.log = sinon.stub();
2232
+ const result = webex.meetings.isNeedHandleLocusDTO(meeting, newLocus);
2233
+ assert.equal(result, true);
2234
+ assert.calledWith(
2235
+ LoggerProxy.logger.log,
2236
+ 'Meetings:index#isNeedHandleMainLocus --> this is a normal main session locusDTO update case'
2237
+ );
2238
+ });
2239
+ it('joined breakout session, self status is moved, return false', () => {
2240
+ newLocus.controls.breakout = {
2241
+ sessionType: 'BREAKOUT',
2242
+ };
2243
+ newLocus.self.state = 'LEFT';
2244
+ newLocus.self.reason = 'MOVED';
2245
+
2246
+ LoggerProxy.logger.log = sinon.stub();
2247
+ const result = webex.meetings.isNeedHandleLocusDTO(meeting, newLocus);
2248
+ assert.equal(result, false);
2249
+ });
2250
+ });
2251
+
2252
+ describe('#getCorrespondingMeetingByLocus', () => {
2253
+ let locus;
2254
+ let mockReturnMeeting = {meeting: 'meeting1'};
2255
+ const mockGetByKey = (keyWillReturnMeeting) => {
2256
+ webex.meetings.meetingCollection.getByKey = sinon.stub().callsFake((key) => {
2257
+ if (key === keyWillReturnMeeting) {
2258
+ return mockReturnMeeting;
2259
+ }
2260
+ return null;
2261
+ });
2262
+ };
2263
+
2264
+ beforeEach(() => {
2265
+ locus = {
2266
+ controls: {},
2267
+ self: {
2268
+ callbackInfo: {
2269
+ callbackAddress: 'address1',
2270
+ },
2271
+ },
2272
+ info: {
2273
+ webExMeetingId: '123456',
2274
+ isUnifiedSpaceMeeting: false,
2275
+ },
2276
+ conversationUrl: 'conversationUrl1',
2277
+ };
2278
+
2279
+ sinon.stub(MeetingsUtil, 'checkForCorrelationId').returns('correlationId1');
2280
+ });
2281
+ afterEach(() => {
2282
+ sinon.restore();
2283
+ });
2284
+ it('check the calls when no meeting found in meetingCollection', () => {
2285
+ mockGetByKey();
2286
+ const result = webex.meetings.getCorrespondingMeetingByLocus({locus, locusUrl: url1});
2287
+ assert.isNull(result);
2288
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 5);
2289
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
2290
+ assert.calledWith(
2291
+ webex.meetings.meetingCollection.getByKey,
2292
+ 'correlationId',
2293
+ 'correlationId1'
2294
+ );
2295
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'sipUri', 'address1');
2296
+ assert.calledWith(
2297
+ webex.meetings.meetingCollection.getByKey,
2298
+ 'conversationUrl',
2299
+ 'conversationUrl1'
2300
+ );
2301
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'meetingNumber', '123456');
2302
+ });
2303
+
2304
+ it('not try getByKey "conversationUrl" when isUnifiedSpaceMeeting is true', () => {
2305
+ mockGetByKey();
2306
+ locus.info.isUnifiedSpaceMeeting = true;
2307
+ const result = webex.meetings.getCorrespondingMeetingByLocus({locus, locusUrl: url1});
2308
+ assert.isNull(result);
2309
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 4);
2310
+ });
2311
+
2312
+ it('check the calls when meeting found by key: locusUrl', () => {
2313
+ mockGetByKey('locusUrl');
2314
+ const result = webex.meetings.getCorrespondingMeetingByLocus({locus, locusUrl: url1});
2315
+ assert.deepEqual(result, mockReturnMeeting);
2316
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 1);
2317
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
2318
+ });
2319
+
2320
+ it('check the calls when meeting found by key: correlationId', () => {
2321
+ mockGetByKey('correlationId');
2322
+ const result = webex.meetings.getCorrespondingMeetingByLocus({locus, locusUrl: url1});
2323
+ assert.deepEqual(result, mockReturnMeeting);
2324
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 2);
2325
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
2326
+ assert.calledWith(
2327
+ webex.meetings.meetingCollection.getByKey,
2328
+ 'correlationId',
2329
+ 'correlationId1'
2330
+ );
2331
+ });
2332
+
2333
+ it('check the calls when meeting found by key: sipUri', () => {
2334
+ mockGetByKey('sipUri');
2335
+ const result = webex.meetings.getCorrespondingMeetingByLocus({locus, locusUrl: url1});
2336
+ assert.deepEqual(result, mockReturnMeeting);
2337
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 3);
2338
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
2339
+ assert.calledWith(
2340
+ webex.meetings.meetingCollection.getByKey,
2341
+ 'correlationId',
2342
+ 'correlationId1'
2343
+ );
2344
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'sipUri', 'address1');
2345
+ });
2346
+
2347
+ it('check the calls when meeting found by key: conversationUrl', () => {
2348
+ mockGetByKey('conversationUrl');
2349
+ const result = webex.meetings.getCorrespondingMeetingByLocus({locus, locusUrl: url1});
2350
+ assert.deepEqual(result, mockReturnMeeting);
2351
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 4);
2352
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
2353
+ assert.calledWith(
2354
+ webex.meetings.meetingCollection.getByKey,
2355
+ 'correlationId',
2356
+ 'correlationId1'
2357
+ );
2358
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'sipUri', 'address1');
2359
+ assert.calledWith(
2360
+ webex.meetings.meetingCollection.getByKey,
2361
+ 'conversationUrl',
2362
+ 'conversationUrl1'
2363
+ );
2364
+ });
2365
+
2366
+ it('check the calls when meeting found by key: meetingNumber', () => {
2367
+ mockGetByKey('meetingNumber');
2368
+ const result = webex.meetings.getCorrespondingMeetingByLocus({locus, locusUrl: url1});
2369
+ assert.deepEqual(result, mockReturnMeeting);
2370
+ assert.callCount(webex.meetings.meetingCollection.getByKey, 5);
2371
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
2372
+ assert.calledWith(
2373
+ webex.meetings.meetingCollection.getByKey,
2374
+ 'correlationId',
2375
+ 'correlationId1'
2376
+ );
2377
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'sipUri', 'address1');
2378
+ assert.calledWith(
2379
+ webex.meetings.meetingCollection.getByKey,
2380
+ 'conversationUrl',
2381
+ 'conversationUrl1'
2382
+ );
2383
+ assert.calledWith(webex.meetings.meetingCollection.getByKey, 'meetingNumber', '123456');
2384
+ });
2385
+ });
2386
+
2387
+ describe('#sortLocusArrayToUpdate', () => {
2388
+ let lociArray;
2389
+ let mainLocus;
2390
+ let breakoutLocus;
2391
+ beforeEach(() => {
2392
+ mainLocus = {
2393
+ url: 'mainUrl1',
2394
+ controls: {
2395
+ breakout: {
2396
+ sessionType: 'MAIN',
2397
+ url: 'breakoutUnifiedUrl1',
2398
+ },
2399
+ },
2400
+ };
2401
+ breakoutLocus = {
2402
+ url: 'breakoutUrl1',
2403
+ controls: {
2404
+ breakout: {
2405
+ sessionType: 'BREAKOUT',
2406
+ url: 'breakoutUnifiedUrl1',
2407
+ },
2408
+ },
2409
+ };
2410
+ lociArray = [mainLocus, breakoutLocus];
2411
+
2412
+ sinon.stub(MeetingsUtil, 'isValidBreakoutLocus').callsFake((locus) => {
2413
+ return locus.url === 'breakoutUrl1';
2414
+ });
2415
+ });
2416
+ afterEach(() => {
2417
+ sinon.restore();
2418
+ });
2419
+
2420
+ it('if both main and breakout locus is in array for non-exist meeting, return main locus to create first', () => {
2421
+ webex.meetings.meetingCollection.getByKey = sinon.stub().returns(undefined);
2422
+ const result = webex.meetings.sortLocusArrayToUpdate(lociArray);
2423
+ assert.deepEqual(result, [mainLocus]);
2424
+ assert.deepEqual(webex.meetings.breakoutLocusForHandleLater, [breakoutLocus]);
2425
+ });
2426
+
2427
+ it('if both main and breakout locus is in array for an exist meeting, return all locus', () => {
2428
+ webex.meetings.meetingCollection.getByKey = sinon.stub().returns({});
2429
+ const result = webex.meetings.sortLocusArrayToUpdate(lociArray);
2430
+ assert.deepEqual(result, [mainLocus, breakoutLocus]);
2431
+ assert.deepEqual(webex.meetings.breakoutLocusForHandleLater, []);
2432
+ });
2433
+
2434
+ it('if the breakout locus has no associated main locus, return all', () => {
2435
+ webex.meetings.meetingCollection.getByKey = sinon.stub().returns({});
2436
+ breakoutLocus.controls.breakout.url = 'testUrl';
2437
+ const result = webex.meetings.sortLocusArrayToUpdate(lociArray);
2438
+ assert.deepEqual(result, [mainLocus, breakoutLocus]);
2439
+ });
2440
+ });
2441
+
2442
+ describe('#checkHandleBreakoutLocus', () => {
2443
+ let breakoutLocus;
2444
+ beforeEach(() => {
2445
+ breakoutLocus = {
2446
+ url: 'breakoutUrl1',
2447
+ controls: {
2448
+ breakout: {
2449
+ sessionType: 'BREAKOUT',
2450
+ url: 'breakoutUnifiedUrl1',
2451
+ },
2452
+ },
2453
+ };
2454
+
2455
+ webex.meetings.handleLocusEvent = sinon.stub();
2456
+ });
2457
+ afterEach(() => {
2458
+ sinon.restore();
2459
+ });
2460
+ it('do nothing if new created locus is null/no cached breakouts for updating', () => {
2461
+ webex.meetings.checkHandleBreakoutLocus(null);
2462
+ webex.meetings.breakoutLocusForHandleLater = null;
2463
+ webex.meetings.checkHandleBreakoutLocus({});
2464
+ webex.meetings.breakoutLocusForHandleLater = [];
2465
+ webex.meetings.checkHandleBreakoutLocus({});
2466
+ assert.notCalled(webex.meetings.handleLocusEvent);
2467
+ });
2468
+
2469
+ it('do nothing if new created locus is breakout locus', () => {
2470
+ webex.meetings.breakoutLocusForHandleLater = [breakoutLocus];
2471
+ webex.meetings.checkHandleBreakoutLocus(breakoutLocus);
2472
+ assert.notCalled(webex.meetings.handleLocusEvent);
2473
+ });
2474
+
2475
+ it('do nothing if no cached locus is associated with the new created locus', () => {
2476
+ webex.meetings.breakoutLocusForHandleLater = [breakoutLocus];
2477
+ webex.meetings.checkHandleBreakoutLocus({
2478
+ controls: {
2479
+ breakout: {
2480
+ sessionType: 'MAIN',
2481
+ url: 'breakoutUnifiedUrl2',
2482
+ },
2483
+ },
2484
+ });
2485
+ assert.notCalled(webex.meetings.handleLocusEvent);
2486
+ });
2487
+
2488
+ it('update the cached breakout locus which associate the new created locus', () => {
2489
+ webex.meetings.breakoutLocusForHandleLater = [breakoutLocus];
2490
+ webex.meetings.checkHandleBreakoutLocus({
2491
+ controls: {
2492
+ breakout: {
2493
+ sessionType: 'MAIN',
2494
+ url: 'breakoutUnifiedUrl1',
2495
+ },
2496
+ },
2497
+ });
2498
+ assert.calledWith(webex.meetings.handleLocusEvent, {
2499
+ locus: breakoutLocus,
2500
+ locusUrl: breakoutLocus.url,
2501
+ });
2502
+ });
2503
+ });
2504
+
2505
+ describe('uploading of logs', () => {
2506
+ let metricsSpy;
2507
+ let meeting;
2508
+
2509
+ beforeEach(async () => {
2510
+ webex.meetings.config.autoUploadLogs = true;
2511
+ webex.meetings.loggerRequest.uploadLogs = sinon.stub().resolves();
2512
+
2513
+ sinon.stub(webex.meetings.meetingInfo, 'fetchInfoOptions').resolves({});
2514
+ sinon.stub(webex.meetings.meetingInfo, 'fetchMeetingInfo').resolves({});
2515
+
2516
+ triggerProxyStub.restore();
2517
+
2518
+ metricsSpy = sinon.stub(Metrics, 'sendBehavioralMetric');
2519
+
2520
+ meeting = await webex.meetings.create('test');
2521
+
2522
+ meeting.locusId = 'locus id';
2523
+ meeting.correlationId = 'correlation id';
2524
+ meeting.locusInfo = {
2525
+ fullState: {lastActive: 'last active', sessionId: 'locus session id'},
2526
+ info: {webExMeetingId: 'meeting id'},
2527
+ };
2528
+ });
2529
+
2530
+ afterEach(() => {
2531
+ sinon.restore();
2532
+ });
2533
+
2534
+ it('sends metrics on success', async () => {
2535
+ await meeting.uploadLogs();
2536
+
2537
+ await testUtils.flushPromises();
2538
+
2539
+ assert.calledOnceWithExactly(metricsSpy, 'js_sdk_upload_logs_success', {
2540
+ callStart: 'last active',
2541
+ correlationId: 'correlation id',
2542
+ feedbackId: 'correlation id',
2543
+ locusId: 'locus id',
2544
+ meetingId: 'meeting id',
2545
+ autoupload: true,
2546
+ locussessionid: 'locus session id',
2547
+ });
2548
+ });
2549
+
2550
+ it('sends metrics on failure', async () => {
2551
+ webex.meetings.loggerRequest.uploadLogs.rejects(new Error('fake error'));
2552
+
2553
+ await meeting.uploadLogs();
2554
+
2555
+ await testUtils.flushPromises();
2556
+
2557
+ assert.calledOnceWithExactly(
2558
+ metricsSpy,
2559
+ 'js_sdk_upload_logs_failure',
2560
+ sinon.match({
2561
+ callStart: 'last active',
2562
+ correlationId: 'correlation id',
2563
+ feedbackId: 'correlation id',
2564
+ locusId: 'locus id',
2565
+ meetingId: 'meeting id',
2566
+ reason: 'fake error',
2567
+ autoupload: true,
2568
+ locussessionid: 'locus session id',
2569
+ })
2570
+ );
2571
+ });
2572
+ });
1371
2573
  });
1372
2574
  });