@strivacity/sdk-core 3.0.3 → 4.0.0-beta.0

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 (327) hide show
  1. package/README.md +1873 -340
  2. package/dist/assets/oidc.cjs +2 -0
  3. package/dist/assets/oidc.cjs.map +1 -0
  4. package/dist/assets/oidc.mjs +2 -0
  5. package/dist/assets/oidc.mjs.map +1 -0
  6. package/dist/assets/server.cjs +2 -0
  7. package/dist/assets/server.cjs.map +1 -0
  8. package/dist/assets/server.mjs +2 -0
  9. package/dist/assets/server.mjs.map +1 -0
  10. package/dist/flows/base.cjs +2 -0
  11. package/dist/flows/base.cjs.map +1 -0
  12. package/dist/flows/base.d.ts +26 -0
  13. package/dist/flows/base.mjs +2 -0
  14. package/dist/flows/base.mjs.map +1 -0
  15. package/dist/flows/embedded.cjs +2 -0
  16. package/dist/flows/embedded.cjs.map +1 -0
  17. package/dist/flows/embedded.d.ts +9 -0
  18. package/dist/flows/embedded.mjs +2 -0
  19. package/dist/flows/embedded.mjs.map +1 -0
  20. package/dist/flows/index.cjs +1 -0
  21. package/dist/flows/index.d.ts +5 -0
  22. package/dist/flows/index.mjs +1 -0
  23. package/dist/flows/native.cjs +2 -0
  24. package/dist/flows/native.cjs.map +1 -0
  25. package/dist/flows/native.d.ts +9 -0
  26. package/dist/flows/native.mjs +2 -0
  27. package/dist/flows/native.mjs.map +1 -0
  28. package/dist/flows/popup.cjs +2 -0
  29. package/dist/flows/popup.cjs.map +1 -0
  30. package/dist/flows/popup.d.ts +9 -0
  31. package/dist/flows/popup.mjs +2 -0
  32. package/dist/flows/popup.mjs.map +1 -0
  33. package/dist/flows/redirect.cjs +2 -0
  34. package/dist/flows/redirect.cjs.map +1 -0
  35. package/dist/flows/redirect.d.ts +9 -0
  36. package/dist/flows/redirect.mjs +2 -0
  37. package/dist/flows/redirect.mjs.map +1 -0
  38. package/dist/handlers/embedded.cjs +2 -0
  39. package/dist/handlers/embedded.cjs.map +1 -0
  40. package/dist/handlers/embedded.d.ts +37 -0
  41. package/dist/handlers/embedded.mjs +2 -0
  42. package/dist/handlers/embedded.mjs.map +1 -0
  43. package/dist/handlers/index.cjs +1 -0
  44. package/dist/handlers/index.d.ts +2 -0
  45. package/dist/handlers/index.mjs +1 -0
  46. package/dist/handlers/native.cjs +2 -0
  47. package/dist/handlers/native.cjs.map +1 -0
  48. package/dist/handlers/native.d.ts +56 -0
  49. package/dist/handlers/native.mjs +2 -0
  50. package/dist/handlers/native.mjs.map +1 -0
  51. package/dist/handlers/popup.cjs +2 -0
  52. package/dist/handlers/popup.cjs.map +1 -0
  53. package/dist/handlers/popup.d.ts +21 -0
  54. package/dist/handlers/popup.mjs +2 -0
  55. package/dist/handlers/popup.mjs.map +1 -0
  56. package/dist/handlers/redirect.cjs +2 -0
  57. package/dist/handlers/redirect.cjs.map +1 -0
  58. package/dist/handlers/redirect.d.ts +18 -0
  59. package/dist/handlers/redirect.mjs +2 -0
  60. package/dist/handlers/redirect.mjs.map +1 -0
  61. package/dist/index.cjs +2 -2
  62. package/dist/index.cjs.map +1 -1
  63. package/dist/index.d.ts +16 -21
  64. package/dist/index.mjs +2 -2
  65. package/dist/index.mjs.map +1 -1
  66. package/dist/server/base.cjs +2 -0
  67. package/dist/server/base.cjs.map +1 -0
  68. package/dist/server/base.d.ts +15 -0
  69. package/dist/server/base.mjs +2 -0
  70. package/dist/server/base.mjs.map +1 -0
  71. package/dist/server/index.cjs +1 -0
  72. package/dist/server/index.d.ts +1 -0
  73. package/dist/server/index.mjs +1 -0
  74. package/dist/storages/cache.cjs +2 -0
  75. package/dist/storages/cache.cjs.map +1 -0
  76. package/dist/storages/cache.d.ts +11 -0
  77. package/dist/storages/cache.mjs +2 -0
  78. package/dist/storages/cache.mjs.map +1 -0
  79. package/dist/storages/index.cjs +1 -0
  80. package/dist/storages/index.d.ts +7 -0
  81. package/dist/storages/index.mjs +1 -0
  82. package/dist/storages/indexeddb.cjs +2 -0
  83. package/dist/storages/indexeddb.cjs.map +1 -0
  84. package/dist/storages/indexeddb.d.ts +10 -0
  85. package/dist/storages/indexeddb.mjs +2 -0
  86. package/dist/storages/indexeddb.mjs.map +1 -0
  87. package/dist/storages/local.cjs +2 -0
  88. package/dist/storages/local.cjs.map +1 -0
  89. package/dist/storages/local.d.ts +7 -0
  90. package/dist/storages/local.mjs +2 -0
  91. package/dist/storages/local.mjs.map +1 -0
  92. package/dist/storages/memory.cjs +2 -0
  93. package/dist/storages/memory.cjs.map +1 -0
  94. package/dist/storages/memory.d.ts +8 -0
  95. package/dist/storages/memory.mjs +2 -0
  96. package/dist/storages/memory.mjs.map +1 -0
  97. package/dist/storages/server.cjs +1 -0
  98. package/dist/storages/server.d.ts +39 -0
  99. package/dist/storages/server.mjs +1 -0
  100. package/dist/storages/session.cjs +2 -0
  101. package/dist/storages/session.cjs.map +1 -0
  102. package/dist/storages/session.d.ts +7 -0
  103. package/dist/storages/session.mjs +2 -0
  104. package/dist/storages/session.mjs.map +1 -0
  105. package/dist/storages/worker.cjs +2 -0
  106. package/dist/storages/worker.cjs.map +1 -0
  107. package/dist/storages/worker.d.ts +46 -0
  108. package/dist/storages/worker.mjs +2 -0
  109. package/dist/storages/worker.mjs.map +1 -0
  110. package/dist/types/common.cjs +0 -0
  111. package/dist/types/common.d.ts +145 -0
  112. package/dist/types/common.mjs +0 -0
  113. package/dist/types/embedded.cjs +0 -0
  114. package/dist/types/embedded.d.ts +103 -0
  115. package/dist/types/embedded.mjs +0 -0
  116. package/dist/types/index.cjs +1 -0
  117. package/dist/types/index.d.ts +7 -0
  118. package/dist/types/index.mjs +1 -0
  119. package/dist/types/native.cjs +0 -0
  120. package/dist/types/native.d.ts +385 -0
  121. package/dist/types/native.mjs +0 -0
  122. package/dist/types/oidc.cjs +2 -0
  123. package/dist/types/oidc.cjs.map +1 -0
  124. package/dist/types/oidc.d.ts +768 -0
  125. package/dist/types/oidc.mjs +2 -0
  126. package/dist/types/oidc.mjs.map +1 -0
  127. package/dist/types/popup.cjs +0 -0
  128. package/dist/types/popup.d.ts +69 -0
  129. package/dist/types/popup.mjs +0 -0
  130. package/dist/types/redirect.cjs +0 -0
  131. package/dist/types/redirect.d.ts +21 -0
  132. package/dist/types/redirect.mjs +0 -0
  133. package/dist/types/server.cjs +0 -0
  134. package/dist/types/server.d.ts +200 -0
  135. package/dist/types/server.mjs +0 -0
  136. package/dist/utils/base64url.cjs +2 -0
  137. package/dist/utils/base64url.cjs.map +1 -0
  138. package/dist/utils/base64url.d.ts +14 -0
  139. package/dist/utils/base64url.mjs +2 -0
  140. package/dist/utils/base64url.mjs.map +1 -0
  141. package/dist/utils/common.cjs +2 -0
  142. package/dist/utils/common.cjs.map +1 -0
  143. package/dist/utils/common.d.ts +23 -0
  144. package/dist/utils/common.mjs +2 -0
  145. package/dist/utils/common.mjs.map +1 -0
  146. package/dist/utils/credentials.cjs +2 -2
  147. package/dist/utils/credentials.cjs.map +1 -1
  148. package/dist/utils/credentials.d.ts +19 -3
  149. package/dist/utils/credentials.mjs +2 -2
  150. package/dist/utils/credentials.mjs.map +1 -1
  151. package/dist/utils/crypto.cjs +2 -2
  152. package/dist/utils/crypto.cjs.map +1 -1
  153. package/dist/utils/crypto.d.ts +16 -26
  154. package/dist/utils/crypto.mjs +2 -2
  155. package/dist/utils/crypto.mjs.map +1 -1
  156. package/dist/utils/dom.cjs +2 -0
  157. package/dist/utils/dom.cjs.map +1 -0
  158. package/dist/utils/dom.d.ts +7 -0
  159. package/dist/utils/dom.mjs +2 -0
  160. package/dist/utils/dom.mjs.map +1 -0
  161. package/dist/utils/errors.cjs +2 -2
  162. package/dist/utils/errors.cjs.map +1 -1
  163. package/dist/utils/errors.d.ts +62 -1
  164. package/dist/utils/errors.mjs +2 -2
  165. package/dist/utils/errors.mjs.map +1 -1
  166. package/dist/utils/httpClient.cjs +2 -0
  167. package/dist/utils/httpClient.cjs.map +1 -0
  168. package/dist/utils/httpClient.d.ts +2 -0
  169. package/dist/utils/httpClient.mjs +2 -0
  170. package/dist/utils/httpClient.mjs.map +1 -0
  171. package/dist/utils/index.cjs +1 -0
  172. package/dist/utils/index.d.ts +12 -0
  173. package/dist/utils/index.mjs +1 -0
  174. package/dist/utils/logging.cjs +2 -0
  175. package/dist/utils/logging.cjs.map +1 -0
  176. package/dist/utils/logging.d.ts +2 -0
  177. package/dist/utils/logging.mjs +2 -0
  178. package/dist/utils/logging.mjs.map +1 -0
  179. package/dist/utils/oidc.cjs +1 -0
  180. package/dist/utils/oidc.d.ts +264 -0
  181. package/dist/utils/oidc.mjs +1 -0
  182. package/dist/utils/server.cjs +1 -0
  183. package/dist/utils/server.d.ts +72 -0
  184. package/dist/utils/server.mjs +1 -0
  185. package/dist/utils/session.cjs +2 -0
  186. package/dist/utils/session.cjs.map +1 -0
  187. package/dist/utils/session.d.ts +29 -0
  188. package/dist/utils/session.mjs +2 -0
  189. package/dist/utils/session.mjs.map +1 -0
  190. package/dist/utils/state.cjs +1 -0
  191. package/dist/utils/state.d.ts +23 -0
  192. package/dist/utils/state.mjs +1 -0
  193. package/package.json +53 -1
  194. package/testing/tests/flows/base.spec.ts +1030 -0
  195. package/testing/tests/flows/embedded.spec.ts +443 -0
  196. package/testing/tests/flows/index.spec.ts +8 -0
  197. package/testing/tests/flows/native.spec.ts +487 -0
  198. package/testing/tests/flows/popup.spec.ts +400 -0
  199. package/testing/tests/flows/redirect.spec.ts +369 -0
  200. package/testing/tests/handlers/embedded.spec.ts +199 -0
  201. package/testing/tests/handlers/index.spec.ts +8 -0
  202. package/testing/tests/handlers/native.spec.ts +283 -0
  203. package/testing/tests/handlers/popup.spec.ts +206 -0
  204. package/testing/tests/handlers/redirect.spec.ts +70 -0
  205. package/testing/tests/index.spec.ts +78 -0
  206. package/testing/tests/server/base.spec.ts +846 -0
  207. package/testing/tests/server/index.spec.ts +15 -0
  208. package/testing/tests/storages/cache.spec.ts +107 -0
  209. package/testing/tests/storages/index.spec.ts +22 -0
  210. package/testing/tests/storages/indexeddb.spec.ts +113 -0
  211. package/testing/tests/storages/local.spec.ts +46 -0
  212. package/testing/tests/storages/memory.spec.ts +51 -0
  213. package/testing/tests/storages/server.spec.ts +435 -0
  214. package/testing/tests/storages/session.spec.ts +46 -0
  215. package/testing/tests/storages/worker.spec.ts +106 -0
  216. package/testing/tests/utils/common.spec.ts +77 -0
  217. package/testing/tests/utils/credentials.spec.ts +195 -0
  218. package/testing/tests/utils/dom.spec.ts +37 -0
  219. package/testing/tests/utils/index.spec.ts +71 -0
  220. package/testing/tests/utils/logging.spec.ts +82 -0
  221. package/testing/tests/utils/oidc.spec.ts +744 -0
  222. package/testing/tests/utils/server.spec.ts +235 -0
  223. package/dist/flows/BaseFlow.cjs +0 -2
  224. package/dist/flows/BaseFlow.cjs.map +0 -1
  225. package/dist/flows/BaseFlow.d.ts +0 -191
  226. package/dist/flows/BaseFlow.mjs +0 -2
  227. package/dist/flows/BaseFlow.mjs.map +0 -1
  228. package/dist/flows/EmbeddedFlow.cjs +0 -2
  229. package/dist/flows/EmbeddedFlow.cjs.map +0 -1
  230. package/dist/flows/EmbeddedFlow.d.ts +0 -26
  231. package/dist/flows/EmbeddedFlow.mjs +0 -2
  232. package/dist/flows/EmbeddedFlow.mjs.map +0 -1
  233. package/dist/flows/NativeFlow.cjs +0 -2
  234. package/dist/flows/NativeFlow.cjs.map +0 -1
  235. package/dist/flows/NativeFlow.d.ts +0 -34
  236. package/dist/flows/NativeFlow.mjs +0 -2
  237. package/dist/flows/NativeFlow.mjs.map +0 -1
  238. package/dist/flows/PopupFlow.cjs +0 -2
  239. package/dist/flows/PopupFlow.cjs.map +0 -1
  240. package/dist/flows/PopupFlow.d.ts +0 -36
  241. package/dist/flows/PopupFlow.mjs +0 -2
  242. package/dist/flows/PopupFlow.mjs.map +0 -1
  243. package/dist/flows/RedirectFlow.cjs +0 -2
  244. package/dist/flows/RedirectFlow.cjs.map +0 -1
  245. package/dist/flows/RedirectFlow.d.ts +0 -38
  246. package/dist/flows/RedirectFlow.mjs +0 -2
  247. package/dist/flows/RedirectFlow.mjs.map +0 -1
  248. package/dist/handlers/BaseFlowHandler.cjs +0 -2
  249. package/dist/handlers/BaseFlowHandler.cjs.map +0 -1
  250. package/dist/handlers/BaseFlowHandler.d.ts +0 -61
  251. package/dist/handlers/BaseFlowHandler.mjs +0 -2
  252. package/dist/handlers/BaseFlowHandler.mjs.map +0 -1
  253. package/dist/handlers/EmbeddedFlowHandler.cjs +0 -2
  254. package/dist/handlers/EmbeddedFlowHandler.cjs.map +0 -1
  255. package/dist/handlers/EmbeddedFlowHandler.d.ts +0 -28
  256. package/dist/handlers/EmbeddedFlowHandler.mjs +0 -2
  257. package/dist/handlers/EmbeddedFlowHandler.mjs.map +0 -1
  258. package/dist/handlers/NativeFlowHandler.cjs +0 -2
  259. package/dist/handlers/NativeFlowHandler.cjs.map +0 -1
  260. package/dist/handlers/NativeFlowHandler.d.ts +0 -31
  261. package/dist/handlers/NativeFlowHandler.mjs +0 -2
  262. package/dist/handlers/NativeFlowHandler.mjs.map +0 -1
  263. package/dist/storages/LocalStorage.cjs +0 -2
  264. package/dist/storages/LocalStorage.cjs.map +0 -1
  265. package/dist/storages/LocalStorage.d.ts +0 -23
  266. package/dist/storages/LocalStorage.mjs +0 -2
  267. package/dist/storages/LocalStorage.mjs.map +0 -1
  268. package/dist/storages/SessionStorage.cjs +0 -2
  269. package/dist/storages/SessionStorage.cjs.map +0 -1
  270. package/dist/storages/SessionStorage.d.ts +0 -23
  271. package/dist/storages/SessionStorage.mjs +0 -2
  272. package/dist/storages/SessionStorage.mjs.map +0 -1
  273. package/dist/types.cjs +0 -2
  274. package/dist/types.cjs.map +0 -1
  275. package/dist/types.d.ts +0 -1263
  276. package/dist/types.mjs +0 -2
  277. package/dist/types.mjs.map +0 -1
  278. package/dist/utils/HttpClient.cjs +0 -2
  279. package/dist/utils/HttpClient.cjs.map +0 -1
  280. package/dist/utils/HttpClient.d.ts +0 -4
  281. package/dist/utils/HttpClient.mjs +0 -2
  282. package/dist/utils/HttpClient.mjs.map +0 -1
  283. package/dist/utils/Logging.cjs +0 -2
  284. package/dist/utils/Logging.cjs.map +0 -1
  285. package/dist/utils/Logging.d.ts +0 -8
  286. package/dist/utils/Logging.mjs +0 -2
  287. package/dist/utils/Logging.mjs.map +0 -1
  288. package/dist/utils/Metadata.cjs +0 -2
  289. package/dist/utils/Metadata.cjs.map +0 -1
  290. package/dist/utils/Metadata.d.ts +0 -98
  291. package/dist/utils/Metadata.mjs +0 -2
  292. package/dist/utils/Metadata.mjs.map +0 -1
  293. package/dist/utils/Session.cjs +0 -2
  294. package/dist/utils/Session.cjs.map +0 -1
  295. package/dist/utils/Session.d.ts +0 -82
  296. package/dist/utils/Session.mjs +0 -2
  297. package/dist/utils/Session.mjs.map +0 -1
  298. package/dist/utils/State.cjs +0 -2
  299. package/dist/utils/State.cjs.map +0 -1
  300. package/dist/utils/State.d.ts +0 -43
  301. package/dist/utils/State.mjs +0 -2
  302. package/dist/utils/State.mjs.map +0 -1
  303. package/dist/utils/base64Url.cjs +0 -2
  304. package/dist/utils/base64Url.cjs.map +0 -1
  305. package/dist/utils/base64Url.d.ts +0 -27
  306. package/dist/utils/base64Url.mjs +0 -2
  307. package/dist/utils/base64Url.mjs.map +0 -1
  308. package/dist/utils/date.cjs +0 -2
  309. package/dist/utils/date.cjs.map +0 -1
  310. package/dist/utils/date.d.ts +0 -6
  311. package/dist/utils/date.mjs +0 -2
  312. package/dist/utils/date.mjs.map +0 -1
  313. package/dist/utils/handlers.cjs +0 -2
  314. package/dist/utils/handlers.cjs.map +0 -1
  315. package/dist/utils/handlers.d.ts +0 -35
  316. package/dist/utils/handlers.mjs +0 -2
  317. package/dist/utils/handlers.mjs.map +0 -1
  318. package/dist/utils/jwt.cjs +0 -2
  319. package/dist/utils/jwt.cjs.map +0 -1
  320. package/dist/utils/jwt.d.ts +0 -34
  321. package/dist/utils/jwt.mjs +0 -2
  322. package/dist/utils/jwt.mjs.map +0 -1
  323. package/dist/utils/object.cjs +0 -2
  324. package/dist/utils/object.cjs.map +0 -1
  325. package/dist/utils/object.d.ts +0 -1
  326. package/dist/utils/object.mjs +0 -2
  327. package/dist/utils/object.mjs.map +0 -1
@@ -0,0 +1,283 @@
1
+ import { beforeEach, describe, test, expect } from 'vitest';
2
+ import { createOptions, createFakeBaseFlow } from '@strivacity/testing/mocks/sdk';
3
+ import { worker, http, HttpResponse } from '@strivacity/testing/mocks/msw';
4
+ import { getDefaultFlowState, ProtocolError, OidcError, ServerError, NetworkError, SessionExpiredError, FallbackError } from '../../../src/utils';
5
+ import { redirectCallbackHandler } from '../../../src/handlers/redirect';
6
+ import { submitFormHandler, startSessionHandler, finalizeSessionHandler } from '../../../src/handlers/native';
7
+
8
+ describe('startSessionHandler', () => {
9
+ let params: Parameters<typeof startSessionHandler>[0];
10
+
11
+ beforeEach(() => {
12
+ globalThis.location.href = 'https://brandtegrity.io';
13
+ params = { base: createFakeBaseFlow(), options: createOptions(), flowState: getDefaultFlowState(), params: {} };
14
+ });
15
+
16
+ test('should start session correctly', async () => {
17
+ worker.use(
18
+ http.get('https://brandtegrity.io/oauth2/auth', () =>
19
+ HttpResponse.text('https://brandtegrity.io/login?session_id=abc123&short_app_id=app&language=hu-HU'),
20
+ ),
21
+ http.post('https://brandtegrity.io/flow/api/v1/init', () => HttpResponse.json({ screen: 'identification' })),
22
+ );
23
+
24
+ await expect(startSessionHandler(params)).resolves.toEqual({ screen: 'identification' });
25
+
26
+ expect(params.flowState.sessionId).toBe('abc123');
27
+ expect(params.flowState.shortAppId).toBe('app');
28
+ expect(params.flowState.language).toBe('hu-HU');
29
+ });
30
+
31
+ test('should continue session correctly', async () => {
32
+ worker.use(http.post('https://brandtegrity.io/flow/api/v1/init', () => HttpResponse.json({ screen: 'identification' })));
33
+ params.params = { sessionId: 'abc123' };
34
+
35
+ await expect(startSessionHandler(params)).resolves.toEqual({ screen: 'identification' });
36
+
37
+ expect(params.base.dispatchEvent).toHaveBeenCalledWith('flowInitiated', []);
38
+ expect(params.flowState.sessionId).toBe('abc123');
39
+ });
40
+
41
+ describe('should exchange the code for tokens correctly', () => {
42
+ test('w/o serverSessionUri', async () => {
43
+ worker.use(http.get('https://brandtegrity.io/oauth2/auth', () => HttpResponse.text('https://brandtegrity.io/callback?code=abc123')));
44
+
45
+ await startSessionHandler(params);
46
+
47
+ expect(globalThis.location.href).not.toBe('https://brandtegrity.io/callback?code=abc123');
48
+ expect(params.base.tokenExchange).toHaveBeenCalledWith({ code: 'abc123' });
49
+ });
50
+
51
+ test('w/ serverSessionUri', async () => {
52
+ worker.use(http.get('https://brandtegrity.io/login', () => HttpResponse.text('https://brandtegrity.io/callback?code=abc123')));
53
+ params.options = createOptions({ serverSessionUri: 'https://brandtegrity.io/login' });
54
+
55
+ await startSessionHandler(params);
56
+
57
+ expect(globalThis.location.href).toBe('https://brandtegrity.io/callback?code=abc123');
58
+ expect(params.base.tokenExchange).not.toHaveBeenCalled();
59
+ });
60
+ });
61
+
62
+ test('should reject with a ProtocolError when neither code nor session_id are present', async () => {
63
+ worker.use(http.get('https://brandtegrity.io/oauth2/auth', () => HttpResponse.text('https://brandtegrity.io/login?something=else')));
64
+
65
+ await expect(startSessionHandler(params)).rejects.toThrow(new ProtocolError('Neither "code" nor "session_id" is present in the response'));
66
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Start login session error', expect.any(ProtocolError));
67
+ });
68
+
69
+ test('should reject with an OidcError when the response contains an error', async () => {
70
+ worker.use(
71
+ http.get('https://brandtegrity.io/oauth2/auth', () =>
72
+ HttpResponse.text('https://brandtegrity.io/login?error=access_denied&error_description=nope&error_uri=https://example.com/error'),
73
+ ),
74
+ );
75
+
76
+ await expect(startSessionHandler(params)).rejects.toThrow(new OidcError('access_denied', 'nope', 'https://example.com/error'));
77
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Start login session error', expect.any(OidcError));
78
+ });
79
+
80
+ test('should reject with a ProtocolError when the redirect URI does not match the configured redirectUri', async () => {
81
+ worker.use(http.get('https://brandtegrity.io/oauth2/auth', () => HttpResponse.text('https://invalid.domain/callback?code=abc123')));
82
+
83
+ await expect(startSessionHandler(params)).rejects.toThrow(new ProtocolError('Invalid redirect URI'));
84
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Start login session error', expect.any(ProtocolError));
85
+ });
86
+
87
+ describe('should reject with an error when the HTTP request fails', () => {
88
+ test('HTTP 400', async () => {
89
+ worker.use(http.get('https://brandtegrity.io/oauth2/auth', () => new HttpResponse(null, { status: 400 })));
90
+
91
+ await expect(startSessionHandler(params)).rejects.toThrow(new ProtocolError('Failed to start login session'));
92
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Start login session error', expect.any(ProtocolError));
93
+ });
94
+
95
+ test('HTTP 429', async () => {
96
+ worker.use(http.get('https://brandtegrity.io/oauth2/auth', () => new HttpResponse(null, { status: 429 })));
97
+
98
+ await expect(startSessionHandler(params)).rejects.toThrow(new ServerError('Failed to start login session', 429));
99
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Start login session error', expect.any(ServerError));
100
+ });
101
+
102
+ test('HTTP 500', async () => {
103
+ worker.use(http.get('https://brandtegrity.io/oauth2/auth', () => new HttpResponse(null, { status: 500 })));
104
+
105
+ await expect(startSessionHandler(params)).rejects.toThrow(new ServerError('Failed to start login session', 500));
106
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Start login session error', expect.any(ServerError));
107
+ });
108
+
109
+ test('HTTP 503', async () => {
110
+ worker.use(http.get('https://brandtegrity.io/oauth2/auth', () => HttpResponse.error()));
111
+
112
+ await expect(startSessionHandler(params)).rejects.toThrow(new NetworkError('Network request failed: https://brandtegrity.io/oauth2/auth'));
113
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Start login session error', expect.any(NetworkError));
114
+ });
115
+ });
116
+ });
117
+
118
+ describe('submitFormHandler', () => {
119
+ let params: Parameters<typeof submitFormHandler>[0];
120
+
121
+ beforeEach(() => {
122
+ globalThis.location.href = 'https://brandtegrity.io';
123
+ params = { base: createFakeBaseFlow(), options: createOptions(), flowState: { ...getDefaultFlowState(), sessionId: 'abc123' } };
124
+ });
125
+
126
+ test('should submit the login form and return the resulting flow state', async () => {
127
+ worker.use(http.post('https://brandtegrity.io/flow/api/v1/init', () => HttpResponse.json({ screen: 'identification' })));
128
+
129
+ await expect(submitFormHandler(params)).resolves.toEqual({ screen: 'identification' });
130
+ });
131
+
132
+ test('should submit to the form-specific endpoint when a formId is provided', async () => {
133
+ worker.use(http.post('https://brandtegrity.io/flow/api/v1/form/identifier', () => HttpResponse.json({ screen: 'password' })));
134
+ params.formId = 'identifier';
135
+
136
+ await expect(submitFormHandler(params)).resolves.toEqual({ screen: 'password' });
137
+ });
138
+
139
+ test('should finalize the session when a finalizeUrl is present in the response', async () => {
140
+ worker.use(
141
+ http.post('https://brandtegrity.io/flow/api/v1/init', () => HttpResponse.json({ finalizeUrl: 'https://brandtegrity.io/finalize' })),
142
+ http.get('https://brandtegrity.io/finalize', () => HttpResponse.text('https://brandtegrity.io/callback?code=abc123')),
143
+ );
144
+ params.options = createOptions({ serverSessionUri: 'https://brandtegrity.io/login' });
145
+
146
+ await submitFormHandler(params);
147
+
148
+ expect(globalThis.location.href).toBe('https://brandtegrity.io/callback?code=abc123');
149
+ });
150
+
151
+ test('should throw when the session ID is missing', async () => {
152
+ params.flowState = getDefaultFlowState();
153
+
154
+ await expect(submitFormHandler(params)).rejects.toThrow(new Error('Session ID is missing. Cannot submit form.'));
155
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Failed to submit form', expect.any(Error));
156
+ });
157
+
158
+ describe('should reject with an error when the HTTP request fails', () => {
159
+ test('HTTP 403 rejects with a SessionExpiredError', async () => {
160
+ worker.use(http.post('https://brandtegrity.io/flow/api/v1/init', () => new HttpResponse(null, { status: 403 })));
161
+
162
+ await expect(submitFormHandler(params)).rejects.toThrow(SessionExpiredError);
163
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Form submission failed', expect.any(SessionExpiredError));
164
+ });
165
+
166
+ test('HTTP 400 with a hostedUrl falls back to the hosted UI', async () => {
167
+ worker.use(
168
+ http.post('https://brandtegrity.io/flow/api/v1/init', () => HttpResponse.json({ hostedUrl: 'https://brandtegrity.io/hosted' }, { status: 400 })),
169
+ );
170
+
171
+ await expect(submitFormHandler(params)).rejects.toThrow(FallbackError);
172
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Form submission failed', expect.any(FallbackError));
173
+ });
174
+
175
+ test('HTTP 400 without a hostedUrl', async () => {
176
+ worker.use(http.post('https://brandtegrity.io/flow/api/v1/init', () => new HttpResponse(null, { status: 400 })));
177
+
178
+ await expect(submitFormHandler(params)).rejects.toThrow(new ProtocolError('Failed to submit login form'));
179
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Form submission failed', expect.any(ProtocolError));
180
+ });
181
+
182
+ test('HTTP 429', async () => {
183
+ worker.use(http.post('https://brandtegrity.io/flow/api/v1/init', () => new HttpResponse(null, { status: 429 })));
184
+
185
+ await expect(submitFormHandler(params)).rejects.toThrow(new ServerError('Failed to submit login form', 429));
186
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Form submission failed', expect.any(ServerError));
187
+ });
188
+
189
+ test('HTTP 500', async () => {
190
+ worker.use(http.post('https://brandtegrity.io/flow/api/v1/init', () => new HttpResponse(null, { status: 500 })));
191
+
192
+ await expect(submitFormHandler(params)).rejects.toThrow(new ServerError('Failed to submit login form', 500));
193
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Form submission failed', expect.any(ServerError));
194
+ });
195
+
196
+ test('HTTP 503', async () => {
197
+ worker.use(http.post('https://brandtegrity.io/flow/api/v1/init', () => HttpResponse.error()));
198
+
199
+ await expect(submitFormHandler(params)).rejects.toThrow(new NetworkError('Network request failed: https://brandtegrity.io/flow/api/v1/init'));
200
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Form submission failed', expect.any(NetworkError));
201
+ });
202
+ });
203
+ });
204
+
205
+ describe('finalizeSessionHandler', () => {
206
+ let params: Parameters<typeof finalizeSessionHandler>[0];
207
+
208
+ beforeEach(() => {
209
+ globalThis.location.href = 'https://brandtegrity.io';
210
+ params = {
211
+ base: createFakeBaseFlow(),
212
+ options: createOptions(),
213
+ flowState: { ...getDefaultFlowState(), sessionId: 'abc123' },
214
+ url: 'https://brandtegrity.io/finalize',
215
+ };
216
+ });
217
+
218
+ describe('should finalize session correctly', () => {
219
+ test('w/o serverSessionUri', async () => {
220
+ worker.use(http.get('https://brandtegrity.io/finalize', () => HttpResponse.text('https://brandtegrity.io/callback?code=abc123')));
221
+ params.options.callbackHandler = redirectCallbackHandler;
222
+
223
+ await finalizeSessionHandler(params);
224
+
225
+ expect(globalThis.location.href).not.toBe('https://brandtegrity.io/callback?code=abc123');
226
+ expect(params.base.tokenExchange).toHaveBeenCalledWith({ code: 'abc123' });
227
+ });
228
+
229
+ test('w/ serverSessionUri', async () => {
230
+ worker.use(http.get('https://brandtegrity.io/finalize', () => HttpResponse.text('https://brandtegrity.io/callback?code=abc123')));
231
+ params.options = createOptions({ serverSessionUri: 'https://brandtegrity.io/login' });
232
+
233
+ await finalizeSessionHandler(params);
234
+
235
+ expect(globalThis.location.href).toBe('https://brandtegrity.io/callback?code=abc123');
236
+ expect(params.base.tokenExchange).not.toHaveBeenCalled();
237
+ });
238
+ });
239
+
240
+ test('should throw when the session ID is missing', async () => {
241
+ params.flowState = getDefaultFlowState();
242
+
243
+ await expect(finalizeSessionHandler(params)).rejects.toThrow(new Error('Session ID is missing. Cannot finalize login session.'));
244
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Failed to finalize login session', expect.any(Error));
245
+ });
246
+
247
+ test('should reject with a ProtocolError when the redirect URI does not match the configured redirectUri', async () => {
248
+ worker.use(http.get('https://brandtegrity.io/finalize', () => HttpResponse.text('https://invalid.domain/callback?code=abc123')));
249
+
250
+ await expect(finalizeSessionHandler(params)).rejects.toThrow(new ProtocolError('Invalid redirect URI'));
251
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Finalize login session error', expect.any(ProtocolError));
252
+ });
253
+
254
+ describe('should reject with an error when the HTTP request fails', () => {
255
+ test('HTTP 400', async () => {
256
+ worker.use(http.get('https://brandtegrity.io/finalize', () => new HttpResponse(null, { status: 400 })));
257
+
258
+ await expect(finalizeSessionHandler(params)).rejects.toThrow(new ProtocolError('Failed to finalize login session'));
259
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Finalize login session error', expect.any(ProtocolError));
260
+ });
261
+
262
+ test('HTTP 429', async () => {
263
+ worker.use(http.get('https://brandtegrity.io/finalize', () => new HttpResponse(null, { status: 429 })));
264
+
265
+ await expect(finalizeSessionHandler(params)).rejects.toThrow(new ServerError('Failed to finalize login session', 429));
266
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Finalize login session error', expect.any(ServerError));
267
+ });
268
+
269
+ test('HTTP 500', async () => {
270
+ worker.use(http.get('https://brandtegrity.io/finalize', () => new HttpResponse(null, { status: 500 })));
271
+
272
+ await expect(finalizeSessionHandler(params)).rejects.toThrow(new ServerError('Failed to finalize login session', 500));
273
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Finalize login session error', expect.any(ServerError));
274
+ });
275
+
276
+ test('HTTP 503', async () => {
277
+ worker.use(http.get('https://brandtegrity.io/finalize', () => HttpResponse.error()));
278
+
279
+ await expect(finalizeSessionHandler(params)).rejects.toThrow(new NetworkError('Network request failed: https://brandtegrity.io/finalize'));
280
+ expect(params.options.logging?.error).toHaveBeenCalledWith('Finalize login session error', expect.any(NetworkError));
281
+ });
282
+ });
283
+ });
@@ -0,0 +1,206 @@
1
+ import { describe, test, expect, vi, afterEach } from 'vitest';
2
+ import { createFakePopup, postMessageFromPopup } from '@strivacity/testing/mocks/window';
3
+ import { popupUrlHandler, popupCallbackHandler } from '../../../src/handlers';
4
+ import { PopupBlockedError, PopupClosedError } from '../../../src/utils';
5
+
6
+ describe('popupUrlHandler', () => {
7
+ afterEach(() => {
8
+ vi.useRealTimers();
9
+ });
10
+
11
+ test('should throw a PopupBlockedError when the popup is blocked', async () => {
12
+ vi.spyOn(window, 'open').mockReturnValue(null);
13
+
14
+ await expect(popupUrlHandler('https://example.com/')).rejects.toThrow(PopupBlockedError);
15
+ });
16
+
17
+ test('should navigate the popup and resolve with the posted message data', async () => {
18
+ const popup = createFakePopup();
19
+ vi.spyOn(window, 'open').mockReturnValue(popup as unknown as Window);
20
+
21
+ const promise = popupUrlHandler('https://example.com/');
22
+
23
+ postMessageFromPopup(popup, { code: 'abc' });
24
+
25
+ await expect(promise).resolves.toEqual({ code: 'abc' });
26
+ expect(popup.location.replace).toHaveBeenCalledWith('https://example.com/');
27
+ expect(popup.focus).toHaveBeenCalled();
28
+ expect(popup.close).toHaveBeenCalled();
29
+ });
30
+
31
+ test('should invoke a popup handler function instead of navigating', async () => {
32
+ const popup = createFakePopup();
33
+ vi.spyOn(window, 'open').mockReturnValue(popup as unknown as Window);
34
+ const handler = vi.fn();
35
+
36
+ const promise = popupUrlHandler(handler);
37
+
38
+ // Let the awaited handler call settle before the internal message listener is registered.
39
+ await Promise.resolve();
40
+
41
+ postMessageFromPopup(popup, { code: 'abc' });
42
+
43
+ await expect(promise).resolves.toEqual({ code: 'abc' });
44
+ expect(handler).toHaveBeenCalledWith(popup);
45
+ expect(popup.location.replace).not.toHaveBeenCalled();
46
+ });
47
+
48
+ test('should ignore messages from an unrelated source', async () => {
49
+ const popup = createFakePopup();
50
+ vi.spyOn(window, 'open').mockReturnValue(popup as unknown as Window);
51
+
52
+ const promise = popupUrlHandler('https://example.com/');
53
+ let settled = false;
54
+ void promise.then(() => (settled = true));
55
+
56
+ postMessageFromPopup({}, { code: 'wrong' });
57
+ await Promise.resolve();
58
+ expect(settled).toBe(false);
59
+
60
+ postMessageFromPopup(popup, { code: 'abc' });
61
+ await expect(promise).resolves.toEqual({ code: 'abc' });
62
+ });
63
+
64
+ test('should reject with a PopupClosedError when the popup is closed by the user', async () => {
65
+ vi.useFakeTimers();
66
+ const popup = createFakePopup();
67
+ vi.spyOn(window, 'open').mockReturnValue(popup as unknown as Window);
68
+
69
+ const promise = popupUrlHandler('https://example.com/');
70
+ const assertion = expect(promise).rejects.toThrow(PopupClosedError);
71
+
72
+ popup.closed = true;
73
+ await vi.advanceTimersByTimeAsync(500);
74
+
75
+ await assertion;
76
+ });
77
+
78
+ test('cleans up its message listener when the popup is closed by the user, not just the timer', async () => {
79
+ vi.useFakeTimers();
80
+ const popup = createFakePopup();
81
+ vi.spyOn(window, 'open').mockReturnValue(popup as unknown as Window);
82
+ const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener');
83
+
84
+ const promise = popupUrlHandler('https://example.com/');
85
+ promise.catch(() => {});
86
+
87
+ popup.closed = true;
88
+ await vi.advanceTimersByTimeAsync(500);
89
+ await expect(promise).rejects.toThrow(PopupClosedError);
90
+
91
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function));
92
+ });
93
+
94
+ test('completion-wins race — a redirect message that beats the close-detection tick resolves exactly once, and a late message afterward is a harmless no-op', async () => {
95
+ for (let i = 0; i < 20; i++) {
96
+ vi.useFakeTimers();
97
+ const popup = createFakePopup();
98
+ vi.spyOn(window, 'open').mockReturnValue(popup as unknown as Window);
99
+
100
+ const promise = popupUrlHandler('https://example.com/');
101
+
102
+ postMessageFromPopup(popup, { code: 'abc' });
103
+ await Promise.resolve();
104
+ popup.closed = true;
105
+ await vi.advanceTimersByTimeAsync(500);
106
+
107
+ await expect(promise).resolves.toEqual({ code: 'abc' });
108
+
109
+ vi.useRealTimers();
110
+ }
111
+ });
112
+
113
+ test('completion-wins race — the close-detection tick beats a redirect message, rejects exactly once, and the late message afterward is a harmless no-op', async () => {
114
+ for (let i = 0; i < 20; i++) {
115
+ vi.useFakeTimers();
116
+ const popup = createFakePopup();
117
+ vi.spyOn(window, 'open').mockReturnValue(popup as unknown as Window);
118
+
119
+ const promise = popupUrlHandler('https://example.com/');
120
+ // NOTE: attached immediately so fake-timer microtask flushing below never reports this as an unhandled rejection.
121
+ promise.catch(() => {});
122
+
123
+ popup.closed = true;
124
+ await vi.advanceTimersByTimeAsync(500);
125
+
126
+ expect(() => postMessageFromPopup(popup, { code: 'abc' })).not.toThrow();
127
+ await expect(promise).rejects.toThrow(PopupClosedError);
128
+
129
+ vi.useRealTimers();
130
+ }
131
+ });
132
+
133
+ test('should close a leftover popup from a previous call', async () => {
134
+ vi.useFakeTimers();
135
+ const firstPopup = createFakePopup();
136
+ vi.spyOn(window, 'open').mockReturnValueOnce(firstPopup as unknown as Window);
137
+
138
+ void popupUrlHandler('https://example.com/first');
139
+
140
+ const secondPopup = createFakePopup();
141
+ vi.spyOn(window, 'open').mockReturnValueOnce(secondPopup as unknown as Window);
142
+
143
+ const secondPromise = popupUrlHandler('https://example.com/second');
144
+
145
+ expect(firstPopup.close).toHaveBeenCalled();
146
+
147
+ postMessageFromPopup(secondPopup, { code: 'abc' });
148
+
149
+ await expect(secondPromise).resolves.toEqual({ code: 'abc' });
150
+ });
151
+ });
152
+
153
+ describe('popupCallbackHandler', () => {
154
+ afterEach(() => {
155
+ globalThis.location.href = 'https://example.com/';
156
+ });
157
+
158
+ test('should post parsed query parameters to the opener window', () => {
159
+ const postMessage = vi.fn();
160
+ vi.spyOn(window, 'opener', 'get').mockReturnValue({ postMessage });
161
+ globalThis.location.href = 'https://example.com/callback?code=abc&state=xyz';
162
+
163
+ void popupCallbackHandler();
164
+
165
+ expect(postMessage).toHaveBeenCalledWith({ code: 'abc', state: 'xyz' }, '*');
166
+ });
167
+
168
+ test('should parse parameters from the fragment when responseMode is "fragment"', () => {
169
+ const postMessage = vi.fn();
170
+ vi.spyOn(window, 'opener', 'get').mockReturnValue({ postMessage });
171
+ globalThis.location.href = 'https://example.com/callback#code=abc&state=xyz';
172
+
173
+ void popupCallbackHandler(undefined, 'fragment');
174
+
175
+ expect(postMessage).toHaveBeenCalledWith({ code: 'abc', state: 'xyz' }, '*');
176
+ });
177
+
178
+ test('should post an invalid_request error when a parameter is duplicated', () => {
179
+ const postMessage = vi.fn();
180
+ vi.spyOn(window, 'opener', 'get').mockReturnValue({ postMessage });
181
+ globalThis.location.href = 'https://example.com/callback?code=abc&code=def';
182
+
183
+ void popupCallbackHandler();
184
+
185
+ expect(postMessage).toHaveBeenCalledWith(expect.objectContaining({ error: 'invalid_request' }), '*');
186
+ });
187
+
188
+ test('should do nothing when there is no opener window', () => {
189
+ vi.spyOn(window, 'opener', 'get').mockReturnValue(null);
190
+ globalThis.location.href = 'https://example.com/callback?code=abc';
191
+
192
+ expect(() => void popupCallbackHandler()).not.toThrow();
193
+ });
194
+
195
+ test('should return a promise that never settles', async () => {
196
+ vi.spyOn(window, 'opener', 'get').mockReturnValue(null);
197
+
198
+ let settled = false;
199
+ void popupCallbackHandler().then(() => (settled = true));
200
+
201
+ await Promise.resolve();
202
+ await Promise.resolve();
203
+
204
+ expect(settled).toBe(false);
205
+ });
206
+ });
@@ -0,0 +1,70 @@
1
+ import { describe, test, expect, vi } from 'vitest';
2
+ import { redirectUrlHandler, redirectCallbackHandler } from '../../../src/handlers';
3
+ import { ProtocolError } from '../../../src/utils';
4
+
5
+ describe('redirectUrlHandler', () => {
6
+ test('should reject when no URL is provided', async () => {
7
+ await expect(redirectUrlHandler()).rejects.toThrow('No URL provided for redirection');
8
+ });
9
+
10
+ test('should call location.assign on the current window by default', async () => {
11
+ const assignSpy = vi.spyOn(window.location, 'assign').mockImplementation(() => undefined);
12
+
13
+ await redirectUrlHandler('https://example.com/');
14
+
15
+ expect(assignSpy).toHaveBeenCalledWith('https://example.com/');
16
+ });
17
+
18
+ test('should call location.replace when locationMethod is "replace"', async () => {
19
+ const replaceSpy = vi.spyOn(window.location, 'replace').mockImplementation(() => undefined);
20
+
21
+ await redirectUrlHandler('https://example.com/', { locationMethod: 'replace' });
22
+
23
+ expect(replaceSpy).toHaveBeenCalledWith('https://example.com/');
24
+ });
25
+
26
+ test('should redirect the top window when targetWindow is "top"', async () => {
27
+ const topAssign = vi.fn();
28
+ vi.spyOn(window, 'top', 'get').mockReturnValue({ location: { assign: topAssign } } as unknown as Window & typeof globalThis);
29
+
30
+ await redirectUrlHandler('https://example.com/', { targetWindow: 'top' });
31
+
32
+ expect(topAssign).toHaveBeenCalledWith('https://example.com/');
33
+ });
34
+
35
+ test('should resolve without navigating when the target window is unavailable', async () => {
36
+ vi.spyOn(window, 'self', 'get').mockReturnValue(undefined as unknown as Window & typeof globalThis);
37
+
38
+ await expect(redirectUrlHandler('https://example.com/')).resolves.toBeUndefined();
39
+ });
40
+ });
41
+
42
+ describe('redirectCallbackHandler', () => {
43
+ test('should reject when no URL is provided', async () => {
44
+ await expect(redirectCallbackHandler('')).rejects.toThrow('No URL provided for redirect callback handling');
45
+ });
46
+
47
+ test('should parse parameters from the query string by default', async () => {
48
+ await expect(redirectCallbackHandler('https://example.com/callback?code=abc&state=xyz')).resolves.toEqual({ code: 'abc', state: 'xyz' });
49
+ });
50
+
51
+ test('should parse parameters from the fragment when responseMode is "fragment"', async () => {
52
+ await expect(redirectCallbackHandler('https://example.com/callback#code=abc&state=xyz', 'fragment')).resolves.toEqual({ code: 'abc', state: 'xyz' });
53
+ });
54
+
55
+ test('should resolve with an empty object when there are no parameters', async () => {
56
+ await expect(redirectCallbackHandler('https://example.com/callback')).resolves.toEqual({});
57
+ });
58
+
59
+ test('should reject with a ProtocolError when a parameter is duplicated', async () => {
60
+ await expect(redirectCallbackHandler('https://example.com/callback?code=abc&code=def')).rejects.toThrow(ProtocolError);
61
+ });
62
+
63
+ test('should not read a code delivered in the fragment when configured for query mode', async () => {
64
+ await expect(redirectCallbackHandler('https://example.com/callback#code=abc&state=xyz')).resolves.toEqual({});
65
+ });
66
+
67
+ test('should not read a code delivered in the query when configured for fragment mode', async () => {
68
+ await expect(redirectCallbackHandler('https://example.com/callback?code=abc&state=xyz', 'fragment')).resolves.toEqual({});
69
+ });
70
+ });
@@ -0,0 +1,78 @@
1
+ import { describe, test, expect, vi, afterEach } from 'vitest';
2
+ import { createOptions } from '@strivacity/testing/mocks/sdk';
3
+ import * as index from '../../src/index';
4
+ import { UnsupportedFlowError } from '../../src/utils/errors';
5
+ import { createRedirectFlow } from '../../src/flows/redirect';
6
+ import { createPopupFlow } from '../../src/flows/popup';
7
+ import { createEmbeddedFlow } from '../../src/flows/embedded';
8
+ import { createNativeFlow } from '../../src/flows/native';
9
+
10
+ vi.mock('../../src/flows/redirect');
11
+ vi.mock('../../src/flows/popup');
12
+ vi.mock('../../src/flows/embedded');
13
+ vi.mock('../../src/flows/native');
14
+
15
+ test('should export the correct things', () => {
16
+ const expectedExports = ['initFlow'];
17
+
18
+ expect(Object.keys(index)).toEqual(expectedExports);
19
+ });
20
+
21
+ describe('initFlow', () => {
22
+ afterEach(() => {
23
+ vi.clearAllMocks();
24
+ });
25
+
26
+ test('mode: "redirect"', () => {
27
+ const options = createOptions({ mode: 'redirect' });
28
+ const flow = { type: 'redirect' };
29
+ vi.mocked(createRedirectFlow).mockReturnValue(flow);
30
+
31
+ expect(index.initFlow(options as never)).toBe(flow);
32
+ expect(createRedirectFlow).toHaveBeenCalledWith(options);
33
+ expect(createPopupFlow).not.toHaveBeenCalled();
34
+ expect(createEmbeddedFlow).not.toHaveBeenCalled();
35
+ expect(createNativeFlow).not.toHaveBeenCalled();
36
+ });
37
+
38
+ test('mode: "popup"', () => {
39
+ const options = createOptions({ mode: 'popup' });
40
+ const flow = { type: 'popup' };
41
+ vi.mocked(createPopupFlow).mockReturnValue(flow);
42
+
43
+ expect(index.initFlow(options as never)).toBe(flow);
44
+ expect(createPopupFlow).toHaveBeenCalledWith(options);
45
+ expect(createRedirectFlow).not.toHaveBeenCalled();
46
+ expect(createEmbeddedFlow).not.toHaveBeenCalled();
47
+ expect(createNativeFlow).not.toHaveBeenCalled();
48
+ });
49
+
50
+ test('mode: "embedded"', () => {
51
+ const options = createOptions({ mode: 'embedded' });
52
+ const flow = { type: 'embedded' };
53
+ vi.mocked(createEmbeddedFlow).mockReturnValue(flow);
54
+
55
+ expect(index.initFlow(options as never)).toBe(flow);
56
+ expect(createEmbeddedFlow).toHaveBeenCalledWith(options);
57
+ expect(createRedirectFlow).not.toHaveBeenCalled();
58
+ expect(createPopupFlow).not.toHaveBeenCalled();
59
+ expect(createNativeFlow).not.toHaveBeenCalled();
60
+ });
61
+
62
+ test('mode: "native"', () => {
63
+ const options = createOptions({ mode: 'native' });
64
+ const flow = { type: 'native' };
65
+ vi.mocked(createNativeFlow).mockReturnValue(flow);
66
+
67
+ expect(index.initFlow(options as never)).toBe(flow);
68
+ expect(createNativeFlow).toHaveBeenCalledWith(options);
69
+ expect(createRedirectFlow).not.toHaveBeenCalled();
70
+ expect(createPopupFlow).not.toHaveBeenCalled();
71
+ expect(createEmbeddedFlow).not.toHaveBeenCalled();
72
+ });
73
+
74
+ test('should throw UnsupportedFlowError when called with an unsupported mode', () => {
75
+ // @ts-expect-error: Testing unsupported mode
76
+ expect(() => index.initFlow({ mode: 'unsupported' })).throw(UnsupportedFlowError);
77
+ });
78
+ });