@sync-in/server 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +2 -1
  3. package/package.json +5 -5
  4. package/server/applications/comments/comments.controller.spec.js +103 -4
  5. package/server/applications/comments/comments.controller.spec.js.map +1 -1
  6. package/server/applications/comments/services/comments-manager.service.spec.js +409 -9
  7. package/server/applications/comments/services/comments-manager.service.spec.js.map +1 -1
  8. package/server/applications/files/adapters/files-indexer-mysql.service.spec.js +333 -0
  9. package/server/applications/files/adapters/files-indexer-mysql.service.spec.js.map +1 -0
  10. package/server/applications/files/constants/routes.js +6 -1
  11. package/server/applications/files/constants/routes.js.map +1 -1
  12. package/server/applications/files/files-only-office.controller.js +11 -0
  13. package/server/applications/files/files-only-office.controller.js.map +1 -1
  14. package/server/applications/files/files-only-office.controller.spec.js +97 -3
  15. package/server/applications/files/files-only-office.controller.spec.js.map +1 -1
  16. package/server/applications/files/files-tasks.controller.spec.js +91 -1
  17. package/server/applications/files/files-tasks.controller.spec.js.map +1 -1
  18. package/server/applications/files/files.controller.spec.js +268 -46
  19. package/server/applications/files/files.controller.spec.js.map +1 -1
  20. package/server/applications/files/guards/files-only-office.guard.spec.js +77 -1
  21. package/server/applications/files/guards/files-only-office.guard.spec.js.map +1 -1
  22. package/server/applications/files/services/files-only-office-manager.service.js +5 -0
  23. package/server/applications/files/services/files-only-office-manager.service.js.map +1 -1
  24. package/server/applications/links/links.controller.spec.js +91 -58
  25. package/server/applications/links/links.controller.spec.js.map +1 -1
  26. package/server/applications/links/services/links-manager.service.js +4 -6
  27. package/server/applications/links/services/links-manager.service.js.map +1 -1
  28. package/server/applications/links/services/links-manager.service.spec.js +378 -14
  29. package/server/applications/links/services/links-manager.service.spec.js.map +1 -1
  30. package/server/applications/links/services/links-queries.service.js +1 -1
  31. package/server/applications/links/services/links-queries.service.js.map +1 -1
  32. package/server/applications/notifications/notifications.controller.spec.js +56 -1
  33. package/server/applications/notifications/notifications.controller.spec.js.map +1 -1
  34. package/server/applications/notifications/services/notifications-manager.service.spec.js +461 -5
  35. package/server/applications/notifications/services/notifications-manager.service.spec.js.map +1 -1
  36. package/server/applications/shares/services/shares-manager.service.spec.js +590 -14
  37. package/server/applications/shares/services/shares-manager.service.spec.js.map +1 -1
  38. package/server/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.js +120 -0
  39. package/server/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.js.map +1 -0
  40. package/server/applications/sync/services/sync-clients-manager.service.spec.js +548 -8
  41. package/server/applications/sync/services/sync-clients-manager.service.spec.js.map +1 -1
  42. package/server/applications/sync/services/sync-manager.service.spec.js +837 -5
  43. package/server/applications/sync/services/sync-manager.service.spec.js.map +1 -1
  44. package/server/applications/sync/services/sync-paths-manager.service.spec.js +900 -7
  45. package/server/applications/sync/services/sync-paths-manager.service.spec.js.map +1 -1
  46. package/server/applications/users/services/admin-users-manager.service.js +22 -24
  47. package/server/applications/users/services/admin-users-manager.service.js.map +1 -1
  48. package/server/applications/users/services/admin-users-manager.service.spec.js +763 -17
  49. package/server/applications/users/services/admin-users-manager.service.spec.js.map +1 -1
  50. package/server/applications/users/services/users-manager.service.js +1 -1
  51. package/server/applications/users/services/users-manager.service.js.map +1 -1
  52. package/server/applications/users/services/users-manager.service.spec.js +938 -49
  53. package/server/applications/users/services/users-manager.service.spec.js.map +1 -1
  54. package/server/applications/webdav/decorators/if-header.decorator.js +4 -1
  55. package/server/applications/webdav/decorators/if-header.decorator.js.map +1 -1
  56. package/server/applications/webdav/filters/webdav.filter.spec.js +77 -0
  57. package/server/applications/webdav/filters/webdav.filter.spec.js.map +1 -0
  58. package/server/applications/webdav/guards/webdav-protocol.guard.js +3 -7
  59. package/server/applications/webdav/guards/webdav-protocol.guard.js.map +1 -1
  60. package/server/applications/webdav/guards/webdav-protocol.guard.spec.js +580 -0
  61. package/server/applications/webdav/guards/webdav-protocol.guard.spec.js.map +1 -0
  62. package/server/applications/webdav/services/webdav-methods.service.spec.js +1582 -3
  63. package/server/applications/webdav/services/webdav-methods.service.spec.js.map +1 -1
  64. package/server/applications/webdav/services/webdav-spaces.service.spec.js +390 -2
  65. package/server/applications/webdav/services/webdav-spaces.service.spec.js.map +1 -1
  66. package/server/applications/webdav/webdav.controller.js +2 -2
  67. package/server/applications/webdav/webdav.controller.js.map +1 -1
  68. package/server/authentication/guards/auth-basic.guard.js.map +1 -1
  69. package/server/authentication/guards/auth-digest.guard.js +1 -2
  70. package/server/authentication/guards/auth-digest.guard.js.map +1 -1
  71. package/server/authentication/guards/auth-local.guard.js.map +1 -1
  72. package/server/infrastructure/context/interceptors/context.interceptor.spec.js +135 -0
  73. package/server/infrastructure/context/interceptors/context.interceptor.spec.js.map +1 -0
  74. package/static/3rdpartylicenses.txt +26 -26
  75. package/static/assets/pdfjs/build/pdf.mjs +1177 -255
  76. package/static/assets/pdfjs/build/pdf.mjs.map +1 -1
  77. package/static/assets/pdfjs/build/pdf.sandbox.mjs +25 -2
  78. package/static/assets/pdfjs/build/pdf.sandbox.mjs.map +1 -1
  79. package/static/assets/pdfjs/build/pdf.worker.mjs +140 -16
  80. package/static/assets/pdfjs/build/pdf.worker.mjs.map +1 -1
  81. package/static/assets/pdfjs/version +1 -1
  82. package/static/assets/pdfjs/web/debugger.css +31 -0
  83. package/static/assets/pdfjs/web/debugger.mjs +144 -2
  84. package/static/assets/pdfjs/web/images/comment-editButton.svg +6 -1
  85. package/static/assets/pdfjs/web/locale/ach/viewer.ftl +0 -63
  86. package/static/assets/pdfjs/web/locale/af/viewer.ftl +0 -71
  87. package/static/assets/pdfjs/web/locale/an/viewer.ftl +0 -63
  88. package/static/assets/pdfjs/web/locale/ast/viewer.ftl +0 -60
  89. package/static/assets/pdfjs/web/locale/az/viewer.ftl +0 -63
  90. package/static/assets/pdfjs/web/locale/be/viewer.ftl +38 -0
  91. package/static/assets/pdfjs/web/locale/bg/viewer.ftl +0 -37
  92. package/static/assets/pdfjs/web/locale/bn/viewer.ftl +0 -63
  93. package/static/assets/pdfjs/web/locale/bo/viewer.ftl +0 -63
  94. package/static/assets/pdfjs/web/locale/br/viewer.ftl +0 -37
  95. package/static/assets/pdfjs/web/locale/brx/viewer.ftl +0 -63
  96. package/static/assets/pdfjs/web/locale/bs/viewer.ftl +22 -0
  97. package/static/assets/pdfjs/web/locale/ca/viewer.ftl +0 -54
  98. package/static/assets/pdfjs/web/locale/cak/viewer.ftl +0 -54
  99. package/static/assets/pdfjs/web/locale/ckb/viewer.ftl +0 -63
  100. package/static/assets/pdfjs/web/locale/cs/viewer.ftl +38 -0
  101. package/static/assets/pdfjs/web/locale/cy/viewer.ftl +38 -0
  102. package/static/assets/pdfjs/web/locale/da/viewer.ftl +38 -0
  103. package/static/assets/pdfjs/web/locale/de/viewer.ftl +38 -0
  104. package/static/assets/pdfjs/web/locale/dsb/viewer.ftl +38 -0
  105. package/static/assets/pdfjs/web/locale/el/viewer.ftl +38 -0
  106. package/static/assets/pdfjs/web/locale/en-CA/viewer.ftl +38 -0
  107. package/static/assets/pdfjs/web/locale/en-GB/viewer.ftl +38 -0
  108. package/static/assets/pdfjs/web/locale/en-US/viewer.ftl +25 -0
  109. package/static/assets/pdfjs/web/locale/eo/viewer.ftl +38 -0
  110. package/static/assets/pdfjs/web/locale/es-AR/viewer.ftl +38 -0
  111. package/static/assets/pdfjs/web/locale/es-CL/viewer.ftl +38 -0
  112. package/static/assets/pdfjs/web/locale/es-MX/viewer.ftl +0 -6
  113. package/static/assets/pdfjs/web/locale/et/viewer.ftl +0 -57
  114. package/static/assets/pdfjs/web/locale/fa/viewer.ftl +0 -37
  115. package/static/assets/pdfjs/web/locale/ff/viewer.ftl +0 -63
  116. package/static/assets/pdfjs/web/locale/fi/viewer.ftl +38 -0
  117. package/static/assets/pdfjs/web/locale/fr/viewer.ftl +38 -0
  118. package/static/assets/pdfjs/web/locale/fy-NL/viewer.ftl +38 -0
  119. package/static/assets/pdfjs/web/locale/ga-IE/viewer.ftl +0 -71
  120. package/static/assets/pdfjs/web/locale/gd/viewer.ftl +0 -54
  121. package/static/assets/pdfjs/web/locale/gl/viewer.ftl +8 -0
  122. package/static/assets/pdfjs/web/locale/gn/viewer.ftl +38 -0
  123. package/static/assets/pdfjs/web/locale/gu-IN/viewer.ftl +0 -63
  124. package/static/assets/pdfjs/web/locale/he/viewer.ftl +38 -0
  125. package/static/assets/pdfjs/web/locale/hi-IN/viewer.ftl +0 -60
  126. package/static/assets/pdfjs/web/locale/hsb/viewer.ftl +38 -0
  127. package/static/assets/pdfjs/web/locale/hu/viewer.ftl +38 -0
  128. package/static/assets/pdfjs/web/locale/hy-AM/viewer.ftl +0 -49
  129. package/static/assets/pdfjs/web/locale/hye/viewer.ftl +0 -60
  130. package/static/assets/pdfjs/web/locale/ia/viewer.ftl +38 -0
  131. package/static/assets/pdfjs/web/locale/is/viewer.ftl +0 -3
  132. package/static/assets/pdfjs/web/locale/it/viewer.ftl +31 -0
  133. package/static/assets/pdfjs/web/locale/ja/viewer.ftl +8 -0
  134. package/static/assets/pdfjs/web/locale/ka/viewer.ftl +48 -10
  135. package/static/assets/pdfjs/web/locale/kab/viewer.ftl +5 -0
  136. package/static/assets/pdfjs/web/locale/kk/viewer.ftl +8 -0
  137. package/static/assets/pdfjs/web/locale/km/viewer.ftl +0 -63
  138. package/static/assets/pdfjs/web/locale/kn/viewer.ftl +0 -71
  139. package/static/assets/pdfjs/web/locale/ko/viewer.ftl +38 -0
  140. package/static/assets/pdfjs/web/locale/lij/viewer.ftl +0 -63
  141. package/static/assets/pdfjs/web/locale/lo/viewer.ftl +0 -54
  142. package/static/assets/pdfjs/web/locale/lt/viewer.ftl +0 -60
  143. package/static/assets/pdfjs/web/locale/ltg/viewer.ftl +0 -63
  144. package/static/assets/pdfjs/web/locale/lv/viewer.ftl +0 -63
  145. package/static/assets/pdfjs/web/locale/meh/viewer.ftl +0 -75
  146. package/static/assets/pdfjs/web/locale/mk/viewer.ftl +0 -63
  147. package/static/assets/pdfjs/web/locale/ml/viewer.ftl +0 -3
  148. package/static/assets/pdfjs/web/locale/mr/viewer.ftl +0 -63
  149. package/static/assets/pdfjs/web/locale/ms/viewer.ftl +0 -63
  150. package/static/assets/pdfjs/web/locale/my/viewer.ftl +0 -71
  151. package/static/assets/pdfjs/web/locale/nb-NO/viewer.ftl +44 -6
  152. package/static/assets/pdfjs/web/locale/ne-NP/viewer.ftl +0 -71
  153. package/static/assets/pdfjs/web/locale/nl/viewer.ftl +38 -0
  154. package/static/assets/pdfjs/web/locale/nn-NO/viewer.ftl +45 -1
  155. package/static/assets/pdfjs/web/locale/oc/viewer.ftl +0 -31
  156. package/static/assets/pdfjs/web/locale/pa-IN/viewer.ftl +38 -0
  157. package/static/assets/pdfjs/web/locale/pl/viewer.ftl +39 -1
  158. package/static/assets/pdfjs/web/locale/pt-BR/viewer.ftl +38 -0
  159. package/static/assets/pdfjs/web/locale/ro/viewer.ftl +355 -1
  160. package/static/assets/pdfjs/web/locale/ru/viewer.ftl +38 -0
  161. package/static/assets/pdfjs/web/locale/sat/viewer.ftl +0 -54
  162. package/static/assets/pdfjs/web/locale/sc/viewer.ftl +0 -38
  163. package/static/assets/pdfjs/web/locale/scn/viewer.ftl +0 -92
  164. package/static/assets/pdfjs/web/locale/sco/viewer.ftl +0 -60
  165. package/static/assets/pdfjs/web/locale/si/viewer.ftl +0 -51
  166. package/static/assets/pdfjs/web/locale/sk/viewer.ftl +38 -0
  167. package/static/assets/pdfjs/web/locale/skr/viewer.ftl +0 -27
  168. package/static/assets/pdfjs/web/locale/sl/viewer.ftl +8 -0
  169. package/static/assets/pdfjs/web/locale/son/viewer.ftl +0 -71
  170. package/static/assets/pdfjs/web/locale/sr/viewer.ftl +0 -33
  171. package/static/assets/pdfjs/web/locale/sv-SE/viewer.ftl +38 -0
  172. package/static/assets/pdfjs/web/locale/szl/viewer.ftl +0 -63
  173. package/static/assets/pdfjs/web/locale/ta/viewer.ftl +0 -63
  174. package/static/assets/pdfjs/web/locale/te/viewer.ftl +0 -60
  175. package/static/assets/pdfjs/web/locale/tg/viewer.ftl +38 -0
  176. package/static/assets/pdfjs/web/locale/tl/viewer.ftl +0 -63
  177. package/static/assets/pdfjs/web/locale/tr/viewer.ftl +40 -2
  178. package/static/assets/pdfjs/web/locale/trs/viewer.ftl +0 -72
  179. package/static/assets/pdfjs/web/locale/ur/viewer.ftl +0 -60
  180. package/static/assets/pdfjs/web/locale/uz/viewer.ftl +0 -71
  181. package/static/assets/pdfjs/web/locale/vi/viewer.ftl +38 -0
  182. package/static/assets/pdfjs/web/locale/wo/viewer.ftl +0 -77
  183. package/static/assets/pdfjs/web/locale/xh/viewer.ftl +0 -71
  184. package/static/assets/pdfjs/web/locale/zh-CN/viewer.ftl +38 -0
  185. package/static/assets/pdfjs/web/locale/zh-TW/viewer.ftl +38 -0
  186. package/static/assets/pdfjs/web/viewer.css +649 -120
  187. package/static/assets/pdfjs/web/viewer.html +19 -0
  188. package/static/assets/pdfjs/web/viewer.mjs +489 -38
  189. package/static/assets/pdfjs/web/viewer.mjs.map +1 -1
  190. package/static/chunk-22EANI6R.js +1 -0
  191. package/static/{chunk-N2LYWNTC.js → chunk-2456KVFZ.js} +1 -1
  192. package/static/{chunk-YCINY2YI.js → chunk-2LVCLKCK.js} +1 -1
  193. package/static/{chunk-5YKWZT33.js → chunk-2V5S7DWD.js} +1 -1
  194. package/static/{chunk-3GC2BQZD.js → chunk-44YDXGNZ.js} +1 -1
  195. package/static/{chunk-W3QXNDI5.js → chunk-4LSJLWYV.js} +1 -1
  196. package/static/{chunk-T55FAU2O.js → chunk-4UT5VH7R.js} +1 -1
  197. package/static/{chunk-VMQMD36Z.js → chunk-5GOMMRRE.js} +1 -1
  198. package/static/{chunk-TXPODW5Q.js → chunk-5J4VRDKB.js} +1 -1
  199. package/static/{chunk-FQ4AFNGE.js → chunk-6PVKNZ7Q.js} +1 -1
  200. package/static/{chunk-XE5YHU5J.js → chunk-BIUNUYZ5.js} +1 -1
  201. package/static/{chunk-X43VWRFP.js → chunk-DFQKHCDR.js} +1 -1
  202. package/static/{chunk-TDQAEVZN.js → chunk-EE2TDTY4.js} +1 -1
  203. package/static/{chunk-MOVWEZ7J.js → chunk-ESNDJ5T6.js} +1 -1
  204. package/static/{chunk-TCFKH6K6.js → chunk-GDKKLLEU.js} +1 -1
  205. package/static/{chunk-VHYIXL7R.js → chunk-GSR2MCQG.js} +1 -1
  206. package/static/{chunk-LJIGRUEF.js → chunk-HR7KS5BR.js} +1 -1
  207. package/static/chunk-HW2H3ISM.js +559 -0
  208. package/static/{chunk-HZA7R43P.js → chunk-IMB3C547.js} +1 -1
  209. package/static/{chunk-B2Y2RNFP.js → chunk-J4ALHUDX.js} +1 -1
  210. package/static/{chunk-PF4K7MVG.js → chunk-KP6LSQTK.js} +1 -1
  211. package/static/{chunk-C23BPTJZ.js → chunk-LUZCOHFN.js} +1 -1
  212. package/static/{chunk-GBCYYDCI.js → chunk-MHSCCXVL.js} +1 -1
  213. package/static/{chunk-PY3BGNJN.js → chunk-OMRQYBXV.js} +1 -1
  214. package/static/chunk-P7CTJ5BG.js +27 -0
  215. package/static/{chunk-Y2CDUS4J.js → chunk-P7PX67IR.js} +4 -4
  216. package/static/{chunk-VMUOUCEI.js → chunk-PPO7DBVO.js} +1 -1
  217. package/static/{chunk-TTQ37MUV.js → chunk-RSS6GYNE.js} +1 -1
  218. package/static/{chunk-WWIC7UW3.js → chunk-SLGGINMR.js} +1 -1
  219. package/static/{chunk-KZQCFEPT.js → chunk-UHD5XD3G.js} +1 -1
  220. package/static/{chunk-TNW2CGK6.js → chunk-UPYYAJCJ.js} +1 -1
  221. package/static/{chunk-6F55D74O.js → chunk-VHYPQ3D4.js} +1 -1
  222. package/static/{chunk-DGVNNICG.js → chunk-YQSDS6BO.js} +1 -1
  223. package/static/{chunk-MTRNPGS4.js → chunk-ZC5NIT55.js} +1 -1
  224. package/static/index.html +1 -1
  225. package/static/main-FYD34UEC.js +7 -0
  226. package/static/chunk-RSNLYAN6.js +0 -560
  227. package/static/chunk-WHMS3PJ3.js +0 -24
  228. package/static/chunk-ZZ3LHYOY.js +0 -1
  229. package/static/main-7LDKYVXO.js +0 -10
@@ -11,6 +11,15 @@ const _notificationscontroller = require("./notifications.controller");
11
11
  const _notificationsmanagerservice = require("./services/notifications-manager.service");
12
12
  describe(_notificationscontroller.NotificationsController.name, ()=>{
13
13
  let controller;
14
+ const notificationsManagerMock = {
15
+ list: jest.fn(),
16
+ wasRead: jest.fn(),
17
+ delete: jest.fn()
18
+ };
19
+ const user = {
20
+ id: 1,
21
+ login: 'john.doe'
22
+ };
14
23
  beforeAll(async ()=>{
15
24
  const module = await _testing.Test.createTestingModule({
16
25
  controllers: [
@@ -19,15 +28,61 @@ describe(_notificationscontroller.NotificationsController.name, ()=>{
19
28
  providers: [
20
29
  {
21
30
  provide: _notificationsmanagerservice.NotificationsManager,
22
- useValue: {}
31
+ useValue: notificationsManagerMock
23
32
  }
24
33
  ]
25
34
  }).compile();
26
35
  controller = module.get(_notificationscontroller.NotificationsController);
27
36
  });
37
+ beforeEach(()=>{
38
+ jest.clearAllMocks();
39
+ });
28
40
  it('should be defined', ()=>{
29
41
  expect(controller).toBeDefined();
30
42
  });
43
+ it('list() should return notifications and call manager.list with user', async ()=>{
44
+ const notifications = [
45
+ {
46
+ id: 10
47
+ },
48
+ {
49
+ id: 11
50
+ }
51
+ ];
52
+ notificationsManagerMock.list.mockResolvedValueOnce(notifications);
53
+ await expect(controller.list(user)).resolves.toBe(notifications);
54
+ expect(notificationsManagerMock.list).toHaveBeenCalledTimes(1);
55
+ expect(notificationsManagerMock.list).toHaveBeenCalledWith(user);
56
+ });
57
+ it('listUnread() should return unread notifications and call manager.list with unread=true', async ()=>{
58
+ const notifications = [
59
+ {
60
+ id: 12
61
+ }
62
+ ];
63
+ notificationsManagerMock.list.mockResolvedValueOnce(notifications);
64
+ await expect(controller.listUnread(user)).resolves.toBe(notifications);
65
+ expect(notificationsManagerMock.list).toHaveBeenCalledTimes(1);
66
+ expect(notificationsManagerMock.list).toHaveBeenCalledWith(user, true);
67
+ });
68
+ it('wasRead() should delegate to manager.wasRead with user and id', ()=>{
69
+ notificationsManagerMock.wasRead.mockReturnValueOnce(undefined);
70
+ controller.wasRead(user, 42);
71
+ expect(notificationsManagerMock.wasRead).toHaveBeenCalledTimes(1);
72
+ expect(notificationsManagerMock.wasRead).toHaveBeenCalledWith(user, 42);
73
+ });
74
+ it('deleteAll() should call manager.delete with user only', async ()=>{
75
+ notificationsManagerMock.delete.mockResolvedValueOnce(undefined);
76
+ await expect(controller.deleteAll(user)).resolves.toBeUndefined();
77
+ expect(notificationsManagerMock.delete).toHaveBeenCalledTimes(1);
78
+ expect(notificationsManagerMock.delete).toHaveBeenCalledWith(user);
79
+ });
80
+ it('delete(:id) should call manager.delete with user and id', async ()=>{
81
+ notificationsManagerMock.delete.mockResolvedValueOnce(undefined);
82
+ await expect(controller.delete(user, 7)).resolves.toBeUndefined();
83
+ expect(notificationsManagerMock.delete).toHaveBeenCalledTimes(1);
84
+ expect(notificationsManagerMock.delete).toHaveBeenCalledWith(user, 7);
85
+ });
31
86
  });
32
87
 
33
88
  //# sourceMappingURL=notifications.controller.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../backend/src/applications/notifications/notifications.controller.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { NotificationsController } from './notifications.controller'\nimport { NotificationsManager } from './services/notifications-manager.service'\n\ndescribe(NotificationsController.name, () => {\n let controller: NotificationsController\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n controllers: [NotificationsController],\n providers: [{ provide: NotificationsManager, useValue: {} }]\n }).compile()\n\n controller = module.get<NotificationsController>(NotificationsController)\n })\n\n it('should be defined', () => {\n expect(controller).toBeDefined()\n })\n})\n"],"names":["describe","NotificationsController","name","controller","beforeAll","module","Test","createTestingModule","controllers","providers","provide","NotificationsManager","useValue","compile","get","it","expect","toBeDefined"],"mappings":"AAAA;;;;CAIC;;;;yBAEmC;yCACI;6CACH;AAErCA,SAASC,gDAAuB,CAACC,IAAI,EAAE;IACrC,IAAIC;IAEJC,UAAU;QACR,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,aAAa;gBAACP,gDAAuB;aAAC;YACtCQ,WAAW;gBAAC;oBAAEC,SAASC,iDAAoB;oBAAEC,UAAU,CAAC;gBAAE;aAAE;QAC9D,GAAGC,OAAO;QAEVV,aAAaE,OAAOS,GAAG,CAA0Bb,gDAAuB;IAC1E;IAEAc,GAAG,qBAAqB;QACtBC,OAAOb,YAAYc,WAAW;IAChC;AACF"}
1
+ {"version":3,"sources":["../../../../backend/src/applications/notifications/notifications.controller.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { NotificationsController } from './notifications.controller'\nimport { NotificationsManager } from './services/notifications-manager.service'\n\ndescribe(NotificationsController.name, () => {\n let controller: NotificationsController\n const notificationsManagerMock: jest.Mocked<NotificationsManager> = {\n list: jest.fn(),\n wasRead: jest.fn(),\n delete: jest.fn()\n } as unknown as jest.Mocked<NotificationsManager>\n\n const user = { id: 1, login: 'john.doe' } as any\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n controllers: [NotificationsController],\n providers: [{ provide: NotificationsManager, useValue: notificationsManagerMock }]\n }).compile()\n\n controller = module.get<NotificationsController>(NotificationsController)\n })\n\n beforeEach(() => {\n jest.clearAllMocks()\n })\n\n it('should be defined', () => {\n expect(controller).toBeDefined()\n })\n\n it('list() should return notifications and call manager.list with user', async () => {\n const notifications = [{ id: 10 }, { id: 11 }] as any\n notificationsManagerMock.list.mockResolvedValueOnce(notifications)\n\n await expect(controller.list(user)).resolves.toBe(notifications)\n expect(notificationsManagerMock.list).toHaveBeenCalledTimes(1)\n expect(notificationsManagerMock.list).toHaveBeenCalledWith(user)\n })\n\n it('listUnread() should return unread notifications and call manager.list with unread=true', async () => {\n const notifications = [{ id: 12 }] as any\n notificationsManagerMock.list.mockResolvedValueOnce(notifications)\n\n await expect(controller.listUnread(user)).resolves.toBe(notifications)\n expect(notificationsManagerMock.list).toHaveBeenCalledTimes(1)\n expect(notificationsManagerMock.list).toHaveBeenCalledWith(user, true)\n })\n\n it('wasRead() should delegate to manager.wasRead with user and id', () => {\n notificationsManagerMock.wasRead.mockReturnValueOnce(undefined as unknown as void)\n\n controller.wasRead(user, 42)\n expect(notificationsManagerMock.wasRead).toHaveBeenCalledTimes(1)\n expect(notificationsManagerMock.wasRead).toHaveBeenCalledWith(user, 42)\n })\n\n it('deleteAll() should call manager.delete with user only', async () => {\n notificationsManagerMock.delete.mockResolvedValueOnce(undefined)\n\n await expect(controller.deleteAll(user)).resolves.toBeUndefined()\n expect(notificationsManagerMock.delete).toHaveBeenCalledTimes(1)\n expect(notificationsManagerMock.delete).toHaveBeenCalledWith(user)\n })\n\n it('delete(:id) should call manager.delete with user and id', async () => {\n notificationsManagerMock.delete.mockResolvedValueOnce(undefined)\n\n await expect(controller.delete(user, 7)).resolves.toBeUndefined()\n expect(notificationsManagerMock.delete).toHaveBeenCalledTimes(1)\n expect(notificationsManagerMock.delete).toHaveBeenCalledWith(user, 7)\n })\n})\n"],"names":["describe","NotificationsController","name","controller","notificationsManagerMock","list","jest","fn","wasRead","delete","user","id","login","beforeAll","module","Test","createTestingModule","controllers","providers","provide","NotificationsManager","useValue","compile","get","beforeEach","clearAllMocks","it","expect","toBeDefined","notifications","mockResolvedValueOnce","resolves","toBe","toHaveBeenCalledTimes","toHaveBeenCalledWith","listUnread","mockReturnValueOnce","undefined","deleteAll","toBeUndefined"],"mappings":"AAAA;;;;CAIC;;;;yBAEmC;yCACI;6CACH;AAErCA,SAASC,gDAAuB,CAACC,IAAI,EAAE;IACrC,IAAIC;IACJ,MAAMC,2BAA8D;QAClEC,MAAMC,KAAKC,EAAE;QACbC,SAASF,KAAKC,EAAE;QAChBE,QAAQH,KAAKC,EAAE;IACjB;IAEA,MAAMG,OAAO;QAAEC,IAAI;QAAGC,OAAO;IAAW;IAExCC,UAAU;QACR,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,aAAa;gBAAChB,gDAAuB;aAAC;YACtCiB,WAAW;gBAAC;oBAAEC,SAASC,iDAAoB;oBAAEC,UAAUjB;gBAAyB;aAAE;QACpF,GAAGkB,OAAO;QAEVnB,aAAaW,OAAOS,GAAG,CAA0BtB,gDAAuB;IAC1E;IAEAuB,WAAW;QACTlB,KAAKmB,aAAa;IACpB;IAEAC,GAAG,qBAAqB;QACtBC,OAAOxB,YAAYyB,WAAW;IAChC;IAEAF,GAAG,sEAAsE;QACvE,MAAMG,gBAAgB;YAAC;gBAAElB,IAAI;YAAG;YAAG;gBAAEA,IAAI;YAAG;SAAE;QAC9CP,yBAAyBC,IAAI,CAACyB,qBAAqB,CAACD;QAEpD,MAAMF,OAAOxB,WAAWE,IAAI,CAACK,OAAOqB,QAAQ,CAACC,IAAI,CAACH;QAClDF,OAAOvB,yBAAyBC,IAAI,EAAE4B,qBAAqB,CAAC;QAC5DN,OAAOvB,yBAAyBC,IAAI,EAAE6B,oBAAoB,CAACxB;IAC7D;IAEAgB,GAAG,0FAA0F;QAC3F,MAAMG,gBAAgB;YAAC;gBAAElB,IAAI;YAAG;SAAE;QAClCP,yBAAyBC,IAAI,CAACyB,qBAAqB,CAACD;QAEpD,MAAMF,OAAOxB,WAAWgC,UAAU,CAACzB,OAAOqB,QAAQ,CAACC,IAAI,CAACH;QACxDF,OAAOvB,yBAAyBC,IAAI,EAAE4B,qBAAqB,CAAC;QAC5DN,OAAOvB,yBAAyBC,IAAI,EAAE6B,oBAAoB,CAACxB,MAAM;IACnE;IAEAgB,GAAG,iEAAiE;QAClEtB,yBAAyBI,OAAO,CAAC4B,mBAAmB,CAACC;QAErDlC,WAAWK,OAAO,CAACE,MAAM;QACzBiB,OAAOvB,yBAAyBI,OAAO,EAAEyB,qBAAqB,CAAC;QAC/DN,OAAOvB,yBAAyBI,OAAO,EAAE0B,oBAAoB,CAACxB,MAAM;IACtE;IAEAgB,GAAG,yDAAyD;QAC1DtB,yBAAyBK,MAAM,CAACqB,qBAAqB,CAACO;QAEtD,MAAMV,OAAOxB,WAAWmC,SAAS,CAAC5B,OAAOqB,QAAQ,CAACQ,aAAa;QAC/DZ,OAAOvB,yBAAyBK,MAAM,EAAEwB,qBAAqB,CAAC;QAC9DN,OAAOvB,yBAAyBK,MAAM,EAAEyB,oBAAoB,CAACxB;IAC/D;IAEAgB,GAAG,2DAA2D;QAC5DtB,yBAAyBK,MAAM,CAACqB,qBAAqB,CAACO;QAEtD,MAAMV,OAAOxB,WAAWM,MAAM,CAACC,MAAM,IAAIqB,QAAQ,CAACQ,aAAa;QAC/DZ,OAAOvB,yBAAyBK,MAAM,EAAEwB,qBAAqB,CAAC;QAC9DN,OAAOvB,yBAAyBK,MAAM,EAAEyB,oBAAoB,CAACxB,MAAM;IACrE;AACF"}
@@ -8,31 +8,131 @@ Object.defineProperty(exports, "__esModule", {
8
8
  });
9
9
  const _testing = require("@nestjs/testing");
10
10
  const _mailerservice = require("../../../infrastructure/mailer/mailer.service");
11
+ const _user = require("../../users/constants/user");
11
12
  const _usersmanagerservice = require("../../users/services/users-manager.service");
13
+ const _notifications = require("../constants/notifications");
14
+ const _websocket = require("../constants/websocket");
15
+ const _models = /*#__PURE__*/ _interop_require_wildcard(require("../mails/models"));
12
16
  const _notificationsgateway = require("../notifications.gateway");
13
17
  const _notificationsmanagerservice = require("./notifications-manager.service");
14
18
  const _notificationsqueriesservice = require("./notifications-queries.service");
19
+ function _getRequireWildcardCache(nodeInterop) {
20
+ if (typeof WeakMap !== "function") return null;
21
+ var cacheBabelInterop = new WeakMap();
22
+ var cacheNodeInterop = new WeakMap();
23
+ return (_getRequireWildcardCache = function(nodeInterop) {
24
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
25
+ })(nodeInterop);
26
+ }
27
+ function _interop_require_wildcard(obj, nodeInterop) {
28
+ if (!nodeInterop && obj && obj.__esModule) {
29
+ return obj;
30
+ }
31
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
32
+ return {
33
+ default: obj
34
+ };
35
+ }
36
+ var cache = _getRequireWildcardCache(nodeInterop);
37
+ if (cache && cache.has(obj)) {
38
+ return cache.get(obj);
39
+ }
40
+ var newObj = {
41
+ __proto__: null
42
+ };
43
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
44
+ for(var key in obj){
45
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
46
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
47
+ if (desc && (desc.get || desc.set)) {
48
+ Object.defineProperty(newObj, key, desc);
49
+ } else {
50
+ newObj[key] = obj[key];
51
+ }
52
+ }
53
+ }
54
+ newObj.default = obj;
55
+ if (cache) {
56
+ cache.set(obj, newObj);
57
+ }
58
+ return newObj;
59
+ }
60
+ // Compact mock for mail generators
61
+ jest.mock('../mails/models', ()=>({
62
+ commentMail: jest.fn(()=>[
63
+ 'comment title',
64
+ 'comment html'
65
+ ]),
66
+ spaceMail: jest.fn(()=>[
67
+ 'space title',
68
+ 'space html'
69
+ ]),
70
+ spaceRootMail: jest.fn(()=>[
71
+ 'spaceRoot title',
72
+ 'spaceRoot html'
73
+ ]),
74
+ shareMail: jest.fn(()=>[
75
+ 'share title',
76
+ 'share html'
77
+ ]),
78
+ linkMail: jest.fn(()=>[
79
+ 'link title',
80
+ 'link html'
81
+ ]),
82
+ syncMail: jest.fn(()=>[
83
+ 'sync title',
84
+ 'sync html'
85
+ ])
86
+ }));
15
87
  describe(_notificationsmanagerservice.NotificationsManager.name, ()=>{
16
88
  let service;
17
- beforeAll(async ()=>{
89
+ const usersManagerMock = {
90
+ getAvatarBase64: jest.fn()
91
+ };
92
+ const mailerMock = {
93
+ available: true,
94
+ sendMails: jest.fn()
95
+ };
96
+ const notificationsQueriesMock = {
97
+ list: jest.fn(),
98
+ usersNotifiedByEmail: jest.fn(),
99
+ create: jest.fn(),
100
+ wasRead: jest.fn(),
101
+ delete: jest.fn()
102
+ };
103
+ const webSocketNotificationsMock = {
104
+ sendMessageToUsers: jest.fn()
105
+ };
106
+ const flushPromises = ()=>new Promise((r)=>setImmediate(r));
107
+ const spyLogger = ()=>jest.spyOn(service.logger, 'error').mockImplementation(()=>undefined);
108
+ beforeEach(async ()=>{
109
+ jest.clearAllMocks();
110
+ mailerMock.available = true;
111
+ mailerMock.sendMails.mockResolvedValue(undefined);
112
+ notificationsQueriesMock.create.mockResolvedValue(undefined);
113
+ notificationsQueriesMock.wasRead.mockResolvedValue(undefined);
114
+ notificationsQueriesMock.delete.mockResolvedValue(undefined);
115
+ notificationsQueriesMock.list.mockResolvedValue([]);
116
+ notificationsQueriesMock.usersNotifiedByEmail.mockResolvedValue([]);
117
+ usersManagerMock.getAvatarBase64.mockResolvedValue('avatar-base64');
18
118
  const module = await _testing.Test.createTestingModule({
19
119
  providers: [
20
120
  _notificationsmanagerservice.NotificationsManager,
21
121
  {
22
122
  provide: _usersmanagerservice.UsersManager,
23
- useValue: {}
123
+ useValue: usersManagerMock
24
124
  },
25
125
  {
26
126
  provide: _mailerservice.Mailer,
27
- useValue: {}
127
+ useValue: mailerMock
28
128
  },
29
129
  {
30
130
  provide: _notificationsgateway.WebSocketNotifications,
31
- useValue: {}
131
+ useValue: webSocketNotificationsMock
32
132
  },
33
133
  {
34
134
  provide: _notificationsqueriesservice.NotificationsQueries,
35
- useValue: {}
135
+ useValue: notificationsQueriesMock
36
136
  }
37
137
  ]
38
138
  }).compile();
@@ -41,6 +141,362 @@ describe(_notificationsmanagerservice.NotificationsManager.name, ()=>{
41
141
  it('should be defined', ()=>{
42
142
  expect(service).toBeDefined();
43
143
  });
144
+ describe('list', ()=>{
145
+ it.each`
146
+ userId | onlyUnread | expected
147
+ ${42} | ${true} | ${true}
148
+ ${1} | ${undefined} | ${false}
149
+ `('should list notifications (userId=$userId, onlyUnread=$onlyUnread)', async ({ userId, onlyUnread, expected })=>{
150
+ const expectedRes = [
151
+ {
152
+ id: userId
153
+ }
154
+ ];
155
+ notificationsQueriesMock.list.mockResolvedValueOnce(expectedRes);
156
+ const res = await service.list({
157
+ id: userId
158
+ }, onlyUnread);
159
+ expect(notificationsQueriesMock.list).toHaveBeenCalledWith(userId, expected);
160
+ expect(res).toBe(expectedRes);
161
+ });
162
+ });
163
+ describe('create', ()=>{
164
+ it('stores, sends WS and no email when filtered list empty (object input)', async ()=>{
165
+ const sendEmailSpy = jest.spyOn(service, 'sendEmailNotification').mockResolvedValue(undefined);
166
+ const toUsers = [
167
+ {
168
+ id: 10,
169
+ email: 'u1@test.tld',
170
+ language: 'en',
171
+ notification: _user.USER_NOTIFICATION.APPLICATION
172
+ },
173
+ {
174
+ id: 11,
175
+ email: 'u2@test.tld',
176
+ language: 'fr',
177
+ notification: _user.USER_NOTIFICATION.APPLICATION
178
+ }
179
+ ];
180
+ await service.create(toUsers, {
181
+ app: _notifications.NOTIFICATION_APP.COMMENTS
182
+ }, {
183
+ author: {
184
+ id: 99,
185
+ login: 'john'
186
+ }
187
+ });
188
+ expect(notificationsQueriesMock.create).toHaveBeenCalledWith(99, [
189
+ 10,
190
+ 11
191
+ ], {
192
+ app: _notifications.NOTIFICATION_APP.COMMENTS
193
+ });
194
+ expect(webSocketNotificationsMock.sendMessageToUsers).toHaveBeenCalledWith([
195
+ 10,
196
+ 11
197
+ ], _websocket.NOTIFICATIONS_WS.EVENTS.NOTIFICATION, 'check');
198
+ expect(sendEmailSpy).not.toHaveBeenCalled();
199
+ expect(notificationsQueriesMock.usersNotifiedByEmail).not.toHaveBeenCalled();
200
+ });
201
+ it('stores, sends WS and email for ids input', async ()=>{
202
+ const sendEmailSpy = jest.spyOn(service, 'sendEmailNotification').mockResolvedValue(undefined);
203
+ const toUserIds = [
204
+ 1,
205
+ 2,
206
+ 3
207
+ ];
208
+ const content = {
209
+ app: _notifications.NOTIFICATION_APP.SHARES
210
+ };
211
+ const emailUsers = [
212
+ {
213
+ id: 1,
214
+ email: 'a@test',
215
+ language: 'en'
216
+ },
217
+ {
218
+ id: 3,
219
+ email: 'c@test',
220
+ language: 'fr'
221
+ }
222
+ ];
223
+ notificationsQueriesMock.usersNotifiedByEmail.mockResolvedValueOnce(emailUsers);
224
+ await service.create(toUserIds, content);
225
+ expect(notificationsQueriesMock.create).toHaveBeenCalledWith(null, toUserIds, content);
226
+ expect(webSocketNotificationsMock.sendMessageToUsers).toHaveBeenCalledWith(toUserIds, _websocket.NOTIFICATIONS_WS.EVENTS.NOTIFICATION, 'check');
227
+ expect(notificationsQueriesMock.usersNotifiedByEmail).toHaveBeenCalledWith(toUserIds);
228
+ expect(sendEmailSpy).toHaveBeenCalledWith(emailUsers, content, undefined);
229
+ });
230
+ it('does not try email when mailer is unavailable', async ()=>{
231
+ mailerMock.available = false;
232
+ const sendEmailSpy = jest.spyOn(service, 'sendEmailNotification').mockResolvedValue(undefined);
233
+ await service.create([
234
+ 7
235
+ ], {
236
+ app: _notifications.NOTIFICATION_APP.SYNC
237
+ }, {
238
+ author: {
239
+ id: 12,
240
+ login: 'jane'
241
+ }
242
+ });
243
+ expect(notificationsQueriesMock.create).toHaveBeenCalledWith(12, [
244
+ 7
245
+ ], {
246
+ app: _notifications.NOTIFICATION_APP.SYNC
247
+ });
248
+ expect(webSocketNotificationsMock.sendMessageToUsers).toHaveBeenCalledWith([
249
+ 7
250
+ ], _websocket.NOTIFICATIONS_WS.EVENTS.NOTIFICATION, 'check');
251
+ expect(notificationsQueriesMock.usersNotifiedByEmail).not.toHaveBeenCalled();
252
+ expect(sendEmailSpy).not.toHaveBeenCalled();
253
+ });
254
+ it('logs error when storeNotification internal try/catch catches create error', async ()=>{
255
+ const loggerSpy = spyLogger();
256
+ notificationsQueriesMock.create.mockRejectedValueOnce(new Error('DB fail'));
257
+ await service.create([
258
+ 1
259
+ ], {
260
+ app: _notifications.NOTIFICATION_APP.LINKS
261
+ });
262
+ await flushPromises();
263
+ expect(loggerSpy).toHaveBeenCalled();
264
+ expect(loggerSpy.mock.calls[0]?.[0]).toMatch(/create/i);
265
+ });
266
+ it('logs error when storeNotification promise rejects (create catch)', async ()=>{
267
+ const loggerSpy = spyLogger();
268
+ jest.spyOn(service, 'storeNotification').mockRejectedValueOnce(new Error('store reject'));
269
+ await service.create([
270
+ 1,
271
+ 2
272
+ ], {
273
+ app: _notifications.NOTIFICATION_APP.SYNC
274
+ }, {
275
+ author: {
276
+ id: 5,
277
+ login: 'xx'
278
+ }
279
+ });
280
+ await flushPromises();
281
+ expect(loggerSpy).toHaveBeenCalled();
282
+ expect(loggerSpy.mock.calls[0]?.[0]).toMatch(/create/i);
283
+ });
284
+ it('logs error when sendEmailNotification rejects (create catch)', async ()=>{
285
+ const loggerSpy = spyLogger();
286
+ notificationsQueriesMock.usersNotifiedByEmail.mockResolvedValueOnce([
287
+ {
288
+ id: 1,
289
+ email: 'a@test',
290
+ language: 'en'
291
+ }
292
+ ]);
293
+ jest.spyOn(service, 'sendEmailNotification').mockRejectedValueOnce(new Error('email reject'));
294
+ await service.create([
295
+ 1
296
+ ], {
297
+ app: _notifications.NOTIFICATION_APP.COMMENTS
298
+ });
299
+ await flushPromises();
300
+ expect(loggerSpy).toHaveBeenCalled();
301
+ expect(loggerSpy.mock.calls[0]?.[0]).toMatch(/create/i);
302
+ });
303
+ });
304
+ describe('wasRead', ()=>{
305
+ it('calls queries.wasRead and logs on error', async ()=>{
306
+ service.wasRead({
307
+ id: 5
308
+ }, 123);
309
+ expect(notificationsQueriesMock.wasRead).toHaveBeenCalledWith(5, 123);
310
+ const loggerSpy = spyLogger();
311
+ notificationsQueriesMock.wasRead.mockRejectedValueOnce(new Error('fail'));
312
+ service.wasRead({
313
+ id: 8
314
+ }, undefined);
315
+ await flushPromises();
316
+ expect(loggerSpy).toHaveBeenCalled();
317
+ expect(loggerSpy.mock.calls[0]?.[0]).toMatch(/wasRead/i);
318
+ });
319
+ });
320
+ describe('delete', ()=>{
321
+ it('forwards to queries.delete', async ()=>{
322
+ await service.delete({
323
+ id: 77
324
+ }, 456);
325
+ expect(notificationsQueriesMock.delete).toHaveBeenCalledWith(77, 456);
326
+ });
327
+ });
328
+ describe('sendEmailNotification', ()=>{
329
+ it('returns early when mailer is not available', async ()=>{
330
+ mailerMock.available = false;
331
+ await service.sendEmailNotification([
332
+ {
333
+ id: 1,
334
+ email: 'a@test',
335
+ language: 'en'
336
+ }
337
+ ], {
338
+ app: _notifications.NOTIFICATION_APP.COMMENTS
339
+ }, {
340
+ author: {
341
+ id: 1,
342
+ login: 'john'
343
+ }
344
+ });
345
+ expect(usersManagerMock.getAvatarBase64).not.toHaveBeenCalled();
346
+ expect(mailerMock.sendMails).not.toHaveBeenCalled();
347
+ });
348
+ it('enriches author avatar and sends mapped mails', async ()=>{
349
+ usersManagerMock.getAvatarBase64.mockResolvedValueOnce('base64-xxx');
350
+ const toUsers = [
351
+ {
352
+ id: 1,
353
+ email: 'a@test',
354
+ language: 'en'
355
+ },
356
+ {
357
+ id: 2,
358
+ email: 'b@test',
359
+ language: 'fr'
360
+ }
361
+ ];
362
+ const options = {
363
+ author: {
364
+ id: 9,
365
+ login: 'jdoe'
366
+ },
367
+ content: 'hello',
368
+ currentUrl: 'https://app.test/path'
369
+ };
370
+ const content = {
371
+ app: _notifications.NOTIFICATION_APP.COMMENTS
372
+ };
373
+ await service.sendEmailNotification(toUsers, content, options);
374
+ expect(usersManagerMock.getAvatarBase64).toHaveBeenCalledWith('jdoe');
375
+ expect(options.author.avatarBase64).toBe('base64-xxx');
376
+ expect(mailerMock.sendMails).toHaveBeenCalledTimes(1);
377
+ expect(mailerMock.sendMails.mock.calls[0][0]).toEqual([
378
+ {
379
+ to: 'a@test',
380
+ subject: 'comment title',
381
+ html: 'comment html'
382
+ },
383
+ {
384
+ to: 'b@test',
385
+ subject: 'comment title',
386
+ html: 'comment html'
387
+ }
388
+ ]);
389
+ });
390
+ it('logs error when sendMails rejects', async ()=>{
391
+ mailerMock.sendMails.mockRejectedValueOnce(new Error('smtp down'));
392
+ const loggerSpy = spyLogger();
393
+ await service.sendEmailNotification([
394
+ {
395
+ id: 1,
396
+ email: 'a@test',
397
+ language: 'en'
398
+ }
399
+ ], {
400
+ app: _notifications.NOTIFICATION_APP.SYNC
401
+ }, {});
402
+ expect(loggerSpy).toHaveBeenCalled();
403
+ expect(loggerSpy.mock.calls[0]?.[0]).toMatch(/sendEmailNotification/i);
404
+ });
405
+ });
406
+ describe('genMail (private) - switch coverage', ()=>{
407
+ const cases = [
408
+ {
409
+ name: 'COMMENTS',
410
+ app: _notifications.NOTIFICATION_APP.COMMENTS,
411
+ fn: 'commentMail',
412
+ options: {
413
+ content: 'c',
414
+ currentUrl: 'u',
415
+ author: {
416
+ id: 1,
417
+ login: 'x'
418
+ }
419
+ }
420
+ },
421
+ {
422
+ name: 'SPACES',
423
+ app: _notifications.NOTIFICATION_APP.SPACES,
424
+ fn: 'spaceMail',
425
+ options: {
426
+ currentUrl: 'u',
427
+ action: 'A'
428
+ }
429
+ },
430
+ {
431
+ name: 'SPACE_ROOTS',
432
+ app: _notifications.NOTIFICATION_APP.SPACE_ROOTS,
433
+ fn: 'spaceRootMail',
434
+ options: {
435
+ currentUrl: 'u',
436
+ author: {
437
+ id: 2,
438
+ login: 'y'
439
+ },
440
+ action: 'B'
441
+ }
442
+ },
443
+ {
444
+ name: 'SHARES',
445
+ app: _notifications.NOTIFICATION_APP.SHARES,
446
+ fn: 'shareMail',
447
+ options: {
448
+ currentUrl: 'u',
449
+ author: {
450
+ id: 3,
451
+ login: 'z'
452
+ },
453
+ action: 'C'
454
+ }
455
+ },
456
+ {
457
+ name: 'LINKS',
458
+ app: _notifications.NOTIFICATION_APP.LINKS,
459
+ fn: 'linkMail',
460
+ options: {
461
+ currentUrl: 'u',
462
+ author: {
463
+ id: 4,
464
+ login: 'w'
465
+ },
466
+ linkUUID: 'uuid',
467
+ action: 'D'
468
+ }
469
+ },
470
+ {
471
+ name: 'SYNC',
472
+ app: _notifications.NOTIFICATION_APP.SYNC,
473
+ fn: 'syncMail',
474
+ options: {
475
+ currentUrl: 'u',
476
+ action: 'E'
477
+ }
478
+ }
479
+ ];
480
+ it.each(cases)('uses $fn for $name', ({ app, fn, options })=>{
481
+ const res = service.genMail('en', {
482
+ app
483
+ }, options);
484
+ expect(res).toEqual([
485
+ `${fn.replace('Mail', '')} title`.replace('spaceRoot', 'spaceRoot'),
486
+ `${fn.replace('Mail', '')} html`.replace('spaceRoot', 'spaceRoot')
487
+ ]);
488
+ expect(_models[fn]).toHaveBeenCalled();
489
+ });
490
+ it('logs error for unhandled app', ()=>{
491
+ const loggerSpy = spyLogger();
492
+ const result = service.genMail('en', {
493
+ app: 99999
494
+ }, {});
495
+ expect(result).toBeUndefined();
496
+ expect(loggerSpy).toHaveBeenCalled();
497
+ expect(loggerSpy.mock.calls[0]?.[0]).toMatch(/case not handled/i);
498
+ });
499
+ });
44
500
  });
45
501
 
46
502
  //# sourceMappingURL=notifications-manager.service.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/applications/notifications/services/notifications-manager.service.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { Mailer } from '../../../infrastructure/mailer/mailer.service'\nimport { UsersManager } from '../../users/services/users-manager.service'\nimport { WebSocketNotifications } from '../notifications.gateway'\nimport { NotificationsManager } from './notifications-manager.service'\nimport { NotificationsQueries } from './notifications-queries.service'\n\ndescribe(NotificationsManager.name, () => {\n let service: NotificationsManager\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n NotificationsManager,\n { provide: UsersManager, useValue: {} },\n {\n provide: Mailer,\n useValue: {}\n },\n { provide: WebSocketNotifications, useValue: {} },\n { provide: NotificationsQueries, useValue: {} }\n ]\n }).compile()\n\n service = module.get<NotificationsManager>(NotificationsManager)\n })\n\n it('should be defined', () => {\n expect(service).toBeDefined()\n })\n})\n"],"names":["describe","NotificationsManager","name","service","beforeAll","module","Test","createTestingModule","providers","provide","UsersManager","useValue","Mailer","WebSocketNotifications","NotificationsQueries","compile","get","it","expect","toBeDefined"],"mappings":"AAAA;;;;CAIC;;;;yBAEmC;+BACb;qCACM;sCACU;6CACF;6CACA;AAErCA,SAASC,iDAAoB,CAACC,IAAI,EAAE;IAClC,IAAIC;IAEJC,UAAU;QACR,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,WAAW;gBACTP,iDAAoB;gBACpB;oBAAEQ,SAASC,iCAAY;oBAAEC,UAAU,CAAC;gBAAE;gBACtC;oBACEF,SAASG,qBAAM;oBACfD,UAAU,CAAC;gBACb;gBACA;oBAAEF,SAASI,4CAAsB;oBAAEF,UAAU,CAAC;gBAAE;gBAChD;oBAAEF,SAASK,iDAAoB;oBAAEH,UAAU,CAAC;gBAAE;aAC/C;QACH,GAAGI,OAAO;QAEVZ,UAAUE,OAAOW,GAAG,CAAuBf,iDAAoB;IACjE;IAEAgB,GAAG,qBAAqB;QACtBC,OAAOf,SAASgB,WAAW;IAC7B;AACF"}
1
+ {"version":3,"sources":["../../../../../backend/src/applications/notifications/services/notifications-manager.service.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { Mailer } from '../../../infrastructure/mailer/mailer.service'\nimport { USER_NOTIFICATION } from '../../users/constants/user'\nimport { UsersManager } from '../../users/services/users-manager.service'\nimport { NOTIFICATION_APP } from '../constants/notifications'\nimport { NOTIFICATIONS_WS } from '../constants/websocket'\nimport * as mailModels from '../mails/models'\nimport { WebSocketNotifications } from '../notifications.gateway'\nimport { NotificationsManager } from './notifications-manager.service'\nimport { NotificationsQueries } from './notifications-queries.service'\n\n// Compact mock for mail generators\njest.mock('../mails/models', () => ({\n commentMail: jest.fn(() => ['comment title', 'comment html']),\n spaceMail: jest.fn(() => ['space title', 'space html']),\n spaceRootMail: jest.fn(() => ['spaceRoot title', 'spaceRoot html']),\n shareMail: jest.fn(() => ['share title', 'share html']),\n linkMail: jest.fn(() => ['link title', 'link html']),\n syncMail: jest.fn(() => ['sync title', 'sync html'])\n}))\n\ndescribe(NotificationsManager.name, () => {\n let service: NotificationsManager\n\n const usersManagerMock = { getAvatarBase64: jest.fn() }\n const mailerMock = { available: true, sendMails: jest.fn() }\n const notificationsQueriesMock = {\n list: jest.fn(),\n usersNotifiedByEmail: jest.fn(),\n create: jest.fn(),\n wasRead: jest.fn(),\n delete: jest.fn()\n }\n const webSocketNotificationsMock = { sendMessageToUsers: jest.fn() }\n\n const flushPromises = () => new Promise<void>((r) => setImmediate(r))\n const spyLogger = () => jest.spyOn((service as any).logger, 'error').mockImplementation(() => undefined as any)\n\n beforeEach(async () => {\n jest.clearAllMocks()\n mailerMock.available = true\n mailerMock.sendMails.mockResolvedValue(undefined)\n notificationsQueriesMock.create.mockResolvedValue(undefined)\n notificationsQueriesMock.wasRead.mockResolvedValue(undefined)\n notificationsQueriesMock.delete.mockResolvedValue(undefined)\n notificationsQueriesMock.list.mockResolvedValue([])\n notificationsQueriesMock.usersNotifiedByEmail.mockResolvedValue([])\n usersManagerMock.getAvatarBase64.mockResolvedValue('avatar-base64')\n\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n NotificationsManager,\n { provide: UsersManager, useValue: usersManagerMock },\n { provide: Mailer, useValue: mailerMock },\n { provide: WebSocketNotifications, useValue: webSocketNotificationsMock },\n { provide: NotificationsQueries, useValue: notificationsQueriesMock }\n ]\n }).compile()\n service = module.get<NotificationsManager>(NotificationsManager)\n })\n\n it('should be defined', () => {\n expect(service).toBeDefined()\n })\n\n describe('list', () => {\n it.each`\n userId | onlyUnread | expected\n ${42} | ${true} | ${true}\n ${1} | ${undefined} | ${false}\n `('should list notifications (userId=$userId, onlyUnread=$onlyUnread)', async ({ userId, onlyUnread, expected }) => {\n const expectedRes = [{ id: userId }] as any\n notificationsQueriesMock.list.mockResolvedValueOnce(expectedRes)\n const res = await service.list({ id: userId } as any, onlyUnread as any)\n expect(notificationsQueriesMock.list).toHaveBeenCalledWith(userId, expected)\n expect(res).toBe(expectedRes)\n })\n })\n\n describe('create', () => {\n it('stores, sends WS and no email when filtered list empty (object input)', async () => {\n const sendEmailSpy = jest.spyOn(service, 'sendEmailNotification').mockResolvedValue(undefined)\n const toUsers = [\n { id: 10, email: 'u1@test.tld', language: 'en', notification: USER_NOTIFICATION.APPLICATION },\n { id: 11, email: 'u2@test.tld', language: 'fr', notification: USER_NOTIFICATION.APPLICATION }\n ]\n await service.create(toUsers as any, { app: NOTIFICATION_APP.COMMENTS } as any, { author: { id: 99, login: 'john' } } as any)\n expect(notificationsQueriesMock.create).toHaveBeenCalledWith(99, [10, 11], { app: NOTIFICATION_APP.COMMENTS })\n expect(webSocketNotificationsMock.sendMessageToUsers).toHaveBeenCalledWith([10, 11], NOTIFICATIONS_WS.EVENTS.NOTIFICATION, 'check')\n expect(sendEmailSpy).not.toHaveBeenCalled()\n expect(notificationsQueriesMock.usersNotifiedByEmail).not.toHaveBeenCalled()\n })\n\n it('stores, sends WS and email for ids input', async () => {\n const sendEmailSpy = jest.spyOn(service, 'sendEmailNotification').mockResolvedValue(undefined)\n const toUserIds = [1, 2, 3]\n const content = { app: NOTIFICATION_APP.SHARES } as any\n const emailUsers = [\n { id: 1, email: 'a@test', language: 'en' },\n { id: 3, email: 'c@test', language: 'fr' }\n ]\n notificationsQueriesMock.usersNotifiedByEmail.mockResolvedValueOnce(emailUsers as any)\n await service.create(toUserIds, content)\n expect(notificationsQueriesMock.create).toHaveBeenCalledWith(null, toUserIds, content)\n expect(webSocketNotificationsMock.sendMessageToUsers).toHaveBeenCalledWith(toUserIds, NOTIFICATIONS_WS.EVENTS.NOTIFICATION, 'check')\n expect(notificationsQueriesMock.usersNotifiedByEmail).toHaveBeenCalledWith(toUserIds)\n expect(sendEmailSpy).toHaveBeenCalledWith(emailUsers as any, content, undefined)\n })\n\n it('does not try email when mailer is unavailable', async () => {\n mailerMock.available = false\n const sendEmailSpy = jest.spyOn(service, 'sendEmailNotification').mockResolvedValue(undefined)\n await service.create([7], { app: NOTIFICATION_APP.SYNC } as any, { author: { id: 12, login: 'jane' } } as any)\n expect(notificationsQueriesMock.create).toHaveBeenCalledWith(12, [7], { app: NOTIFICATION_APP.SYNC })\n expect(webSocketNotificationsMock.sendMessageToUsers).toHaveBeenCalledWith([7], NOTIFICATIONS_WS.EVENTS.NOTIFICATION, 'check')\n expect(notificationsQueriesMock.usersNotifiedByEmail).not.toHaveBeenCalled()\n expect(sendEmailSpy).not.toHaveBeenCalled()\n })\n\n it('logs error when storeNotification internal try/catch catches create error', async () => {\n const loggerSpy = spyLogger()\n notificationsQueriesMock.create.mockRejectedValueOnce(new Error('DB fail'))\n await service.create([1], { app: NOTIFICATION_APP.LINKS } as any)\n await flushPromises()\n expect(loggerSpy).toHaveBeenCalled()\n expect(loggerSpy.mock.calls[0]?.[0] as string).toMatch(/create/i)\n })\n\n it('logs error when storeNotification promise rejects (create catch)', async () => {\n const loggerSpy = spyLogger()\n jest.spyOn<any, any>(service as any, 'storeNotification').mockRejectedValueOnce(new Error('store reject'))\n await service.create([1, 2], { app: NOTIFICATION_APP.SYNC } as any, { author: { id: 5, login: 'xx' } } as any)\n await flushPromises()\n expect(loggerSpy).toHaveBeenCalled()\n expect(loggerSpy.mock.calls[0]?.[0] as string).toMatch(/create/i)\n })\n\n it('logs error when sendEmailNotification rejects (create catch)', async () => {\n const loggerSpy = spyLogger()\n notificationsQueriesMock.usersNotifiedByEmail.mockResolvedValueOnce([{ id: 1, email: 'a@test', language: 'en' }] as any)\n jest.spyOn(service, 'sendEmailNotification').mockRejectedValueOnce(new Error('email reject'))\n await service.create([1], { app: NOTIFICATION_APP.COMMENTS } as any)\n await flushPromises()\n expect(loggerSpy).toHaveBeenCalled()\n expect(loggerSpy.mock.calls[0]?.[0] as string).toMatch(/create/i)\n })\n })\n\n describe('wasRead', () => {\n it('calls queries.wasRead and logs on error', async () => {\n service.wasRead({ id: 5 } as any, 123)\n expect(notificationsQueriesMock.wasRead).toHaveBeenCalledWith(5, 123)\n const loggerSpy = spyLogger()\n notificationsQueriesMock.wasRead.mockRejectedValueOnce(new Error('fail'))\n service.wasRead({ id: 8 } as any, undefined)\n await flushPromises()\n expect(loggerSpy).toHaveBeenCalled()\n expect(loggerSpy.mock.calls[0]?.[0] as string).toMatch(/wasRead/i)\n })\n })\n\n describe('delete', () => {\n it('forwards to queries.delete', async () => {\n await service.delete({ id: 77 } as any, 456)\n expect(notificationsQueriesMock.delete).toHaveBeenCalledWith(77, 456)\n })\n })\n\n describe('sendEmailNotification', () => {\n it('returns early when mailer is not available', async () => {\n mailerMock.available = false\n await service.sendEmailNotification(\n [{ id: 1, email: 'a@test', language: 'en' }] as any,\n { app: NOTIFICATION_APP.COMMENTS } as any,\n {\n author: { id: 1, login: 'john' }\n } as any\n )\n expect(usersManagerMock.getAvatarBase64).not.toHaveBeenCalled()\n expect(mailerMock.sendMails).not.toHaveBeenCalled()\n })\n\n it('enriches author avatar and sends mapped mails', async () => {\n usersManagerMock.getAvatarBase64.mockResolvedValueOnce('base64-xxx')\n const toUsers = [\n { id: 1, email: 'a@test', language: 'en' },\n { id: 2, email: 'b@test', language: 'fr' }\n ]\n const options: any = { author: { id: 9, login: 'jdoe' }, content: 'hello', currentUrl: 'https://app.test/path' }\n const content = { app: NOTIFICATION_APP.COMMENTS } as any\n await service.sendEmailNotification(toUsers as any, content, options)\n expect(usersManagerMock.getAvatarBase64).toHaveBeenCalledWith('jdoe')\n expect(options.author.avatarBase64).toBe('base64-xxx')\n expect(mailerMock.sendMails).toHaveBeenCalledTimes(1)\n expect((mailerMock.sendMails as jest.Mock).mock.calls[0][0]).toEqual([\n { to: 'a@test', subject: 'comment title', html: 'comment html' },\n { to: 'b@test', subject: 'comment title', html: 'comment html' }\n ])\n })\n\n it('logs error when sendMails rejects', async () => {\n mailerMock.sendMails.mockRejectedValueOnce(new Error('smtp down'))\n const loggerSpy = spyLogger()\n await service.sendEmailNotification([{ id: 1, email: 'a@test', language: 'en' }] as any, { app: NOTIFICATION_APP.SYNC } as any, {} as any)\n expect(loggerSpy).toHaveBeenCalled()\n expect(loggerSpy.mock.calls[0]?.[0] as string).toMatch(/sendEmailNotification/i)\n })\n })\n\n describe('genMail (private) - switch coverage', () => {\n const cases = [\n {\n name: 'COMMENTS',\n app: NOTIFICATION_APP.COMMENTS,\n fn: 'commentMail',\n options: { content: 'c', currentUrl: 'u', author: { id: 1, login: 'x' } }\n },\n { name: 'SPACES', app: NOTIFICATION_APP.SPACES, fn: 'spaceMail', options: { currentUrl: 'u', action: 'A' } },\n {\n name: 'SPACE_ROOTS',\n app: NOTIFICATION_APP.SPACE_ROOTS,\n fn: 'spaceRootMail',\n options: { currentUrl: 'u', author: { id: 2, login: 'y' }, action: 'B' }\n },\n { name: 'SHARES', app: NOTIFICATION_APP.SHARES, fn: 'shareMail', options: { currentUrl: 'u', author: { id: 3, login: 'z' }, action: 'C' } },\n {\n name: 'LINKS',\n app: NOTIFICATION_APP.LINKS,\n fn: 'linkMail',\n options: { currentUrl: 'u', author: { id: 4, login: 'w' }, linkUUID: 'uuid', action: 'D' }\n },\n { name: 'SYNC', app: NOTIFICATION_APP.SYNC, fn: 'syncMail', options: { currentUrl: 'u', action: 'E' } }\n ] as const\n\n it.each(cases)('uses $fn for $name', ({ app, fn, options }) => {\n const res = (service as any).genMail('en', { app } as any, options as any)\n expect(res).toEqual([\n `${fn.replace('Mail', '')} title`.replace('spaceRoot', 'spaceRoot'),\n `${fn.replace('Mail', '')} html`.replace('spaceRoot', 'spaceRoot')\n ])\n expect((mailModels as any)[fn]).toHaveBeenCalled()\n })\n\n it('logs error for unhandled app', () => {\n const loggerSpy = spyLogger()\n const result = (service as any).genMail('en', { app: 99999 } as any, {} as any)\n expect(result).toBeUndefined()\n expect(loggerSpy).toHaveBeenCalled()\n expect(loggerSpy.mock.calls[0]?.[0] as string).toMatch(/case not handled/i)\n })\n })\n})\n"],"names":["jest","mock","commentMail","fn","spaceMail","spaceRootMail","shareMail","linkMail","syncMail","describe","NotificationsManager","name","service","usersManagerMock","getAvatarBase64","mailerMock","available","sendMails","notificationsQueriesMock","list","usersNotifiedByEmail","create","wasRead","delete","webSocketNotificationsMock","sendMessageToUsers","flushPromises","Promise","r","setImmediate","spyLogger","spyOn","logger","mockImplementation","undefined","beforeEach","clearAllMocks","mockResolvedValue","module","Test","createTestingModule","providers","provide","UsersManager","useValue","Mailer","WebSocketNotifications","NotificationsQueries","compile","get","it","expect","toBeDefined","each","userId","onlyUnread","expected","expectedRes","id","mockResolvedValueOnce","res","toHaveBeenCalledWith","toBe","sendEmailSpy","toUsers","email","language","notification","USER_NOTIFICATION","APPLICATION","app","NOTIFICATION_APP","COMMENTS","author","login","NOTIFICATIONS_WS","EVENTS","NOTIFICATION","not","toHaveBeenCalled","toUserIds","content","SHARES","emailUsers","SYNC","loggerSpy","mockRejectedValueOnce","Error","LINKS","calls","toMatch","sendEmailNotification","options","currentUrl","avatarBase64","toHaveBeenCalledTimes","toEqual","to","subject","html","cases","SPACES","action","SPACE_ROOTS","linkUUID","genMail","replace","mailModels","result","toBeUndefined"],"mappings":"AAAA;;;;CAIC;;;;yBAEmC;+BACb;sBACW;qCACL;+BACI;2BACA;gEACL;sCACW;6CACF;6CACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAErC,mCAAmC;AACnCA,KAAKC,IAAI,CAAC,mBAAmB,IAAO,CAAA;QAClCC,aAAaF,KAAKG,EAAE,CAAC,IAAM;gBAAC;gBAAiB;aAAe;QAC5DC,WAAWJ,KAAKG,EAAE,CAAC,IAAM;gBAAC;gBAAe;aAAa;QACtDE,eAAeL,KAAKG,EAAE,CAAC,IAAM;gBAAC;gBAAmB;aAAiB;QAClEG,WAAWN,KAAKG,EAAE,CAAC,IAAM;gBAAC;gBAAe;aAAa;QACtDI,UAAUP,KAAKG,EAAE,CAAC,IAAM;gBAAC;gBAAc;aAAY;QACnDK,UAAUR,KAAKG,EAAE,CAAC,IAAM;gBAAC;gBAAc;aAAY;IACrD,CAAA;AAEAM,SAASC,iDAAoB,CAACC,IAAI,EAAE;IAClC,IAAIC;IAEJ,MAAMC,mBAAmB;QAAEC,iBAAiBd,KAAKG,EAAE;IAAG;IACtD,MAAMY,aAAa;QAAEC,WAAW;QAAMC,WAAWjB,KAAKG,EAAE;IAAG;IAC3D,MAAMe,2BAA2B;QAC/BC,MAAMnB,KAAKG,EAAE;QACbiB,sBAAsBpB,KAAKG,EAAE;QAC7BkB,QAAQrB,KAAKG,EAAE;QACfmB,SAAStB,KAAKG,EAAE;QAChBoB,QAAQvB,KAAKG,EAAE;IACjB;IACA,MAAMqB,6BAA6B;QAAEC,oBAAoBzB,KAAKG,EAAE;IAAG;IAEnE,MAAMuB,gBAAgB,IAAM,IAAIC,QAAc,CAACC,IAAMC,aAAaD;IAClE,MAAME,YAAY,IAAM9B,KAAK+B,KAAK,CAAC,AAACnB,QAAgBoB,MAAM,EAAE,SAASC,kBAAkB,CAAC,IAAMC;IAE9FC,WAAW;QACTnC,KAAKoC,aAAa;QAClBrB,WAAWC,SAAS,GAAG;QACvBD,WAAWE,SAAS,CAACoB,iBAAiB,CAACH;QACvChB,yBAAyBG,MAAM,CAACgB,iBAAiB,CAACH;QAClDhB,yBAAyBI,OAAO,CAACe,iBAAiB,CAACH;QACnDhB,yBAAyBK,MAAM,CAACc,iBAAiB,CAACH;QAClDhB,yBAAyBC,IAAI,CAACkB,iBAAiB,CAAC,EAAE;QAClDnB,yBAAyBE,oBAAoB,CAACiB,iBAAiB,CAAC,EAAE;QAClExB,iBAAiBC,eAAe,CAACuB,iBAAiB,CAAC;QAEnD,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,WAAW;gBACT/B,iDAAoB;gBACpB;oBAAEgC,SAASC,iCAAY;oBAAEC,UAAU/B;gBAAiB;gBACpD;oBAAE6B,SAASG,qBAAM;oBAAED,UAAU7B;gBAAW;gBACxC;oBAAE2B,SAASI,4CAAsB;oBAAEF,UAAUpB;gBAA2B;gBACxE;oBAAEkB,SAASK,iDAAoB;oBAAEH,UAAU1B;gBAAyB;aACrE;QACH,GAAG8B,OAAO;QACVpC,UAAU0B,OAAOW,GAAG,CAAuBvC,iDAAoB;IACjE;IAEAwC,GAAG,qBAAqB;QACtBC,OAAOvC,SAASwC,WAAW;IAC7B;IAEA3C,SAAS,QAAQ;QACfyC,GAAGG,IAAI,CAAC;;MAEN,EAAE,GAAG,IAAI,EAAE,KAAK,QAAQ,EAAE,KAAK;MAC/B,EAAE,EAAE,KAAK,EAAEnB,UAAU,GAAG,EAAE,MAAM;IAClC,CAAC,CAAC,sEAAsE,OAAO,EAAEoB,MAAM,EAAEC,UAAU,EAAEC,QAAQ,EAAE;YAC7G,MAAMC,cAAc;gBAAC;oBAAEC,IAAIJ;gBAAO;aAAE;YACpCpC,yBAAyBC,IAAI,CAACwC,qBAAqB,CAACF;YACpD,MAAMG,MAAM,MAAMhD,QAAQO,IAAI,CAAC;gBAAEuC,IAAIJ;YAAO,GAAUC;YACtDJ,OAAOjC,yBAAyBC,IAAI,EAAE0C,oBAAoB,CAACP,QAAQE;YACnEL,OAAOS,KAAKE,IAAI,CAACL;QACnB;IACF;IAEAhD,SAAS,UAAU;QACjByC,GAAG,yEAAyE;YAC1E,MAAMa,eAAe/D,KAAK+B,KAAK,CAACnB,SAAS,yBAAyByB,iBAAiB,CAACH;YACpF,MAAM8B,UAAU;gBACd;oBAAEN,IAAI;oBAAIO,OAAO;oBAAeC,UAAU;oBAAMC,cAAcC,uBAAiB,CAACC,WAAW;gBAAC;gBAC5F;oBAAEX,IAAI;oBAAIO,OAAO;oBAAeC,UAAU;oBAAMC,cAAcC,uBAAiB,CAACC,WAAW;gBAAC;aAC7F;YACD,MAAMzD,QAAQS,MAAM,CAAC2C,SAAgB;gBAAEM,KAAKC,+BAAgB,CAACC,QAAQ;YAAC,GAAU;gBAAEC,QAAQ;oBAAEf,IAAI;oBAAIgB,OAAO;gBAAO;YAAE;YACpHvB,OAAOjC,yBAAyBG,MAAM,EAAEwC,oBAAoB,CAAC,IAAI;gBAAC;gBAAI;aAAG,EAAE;gBAAES,KAAKC,+BAAgB,CAACC,QAAQ;YAAC;YAC5GrB,OAAO3B,2BAA2BC,kBAAkB,EAAEoC,oBAAoB,CAAC;gBAAC;gBAAI;aAAG,EAAEc,2BAAgB,CAACC,MAAM,CAACC,YAAY,EAAE;YAC3H1B,OAAOY,cAAce,GAAG,CAACC,gBAAgB;YACzC5B,OAAOjC,yBAAyBE,oBAAoB,EAAE0D,GAAG,CAACC,gBAAgB;QAC5E;QAEA7B,GAAG,4CAA4C;YAC7C,MAAMa,eAAe/D,KAAK+B,KAAK,CAACnB,SAAS,yBAAyByB,iBAAiB,CAACH;YACpF,MAAM8C,YAAY;gBAAC;gBAAG;gBAAG;aAAE;YAC3B,MAAMC,UAAU;gBAAEX,KAAKC,+BAAgB,CAACW,MAAM;YAAC;YAC/C,MAAMC,aAAa;gBACjB;oBAAEzB,IAAI;oBAAGO,OAAO;oBAAUC,UAAU;gBAAK;gBACzC;oBAAER,IAAI;oBAAGO,OAAO;oBAAUC,UAAU;gBAAK;aAC1C;YACDhD,yBAAyBE,oBAAoB,CAACuC,qBAAqB,CAACwB;YACpE,MAAMvE,QAAQS,MAAM,CAAC2D,WAAWC;YAChC9B,OAAOjC,yBAAyBG,MAAM,EAAEwC,oBAAoB,CAAC,MAAMmB,WAAWC;YAC9E9B,OAAO3B,2BAA2BC,kBAAkB,EAAEoC,oBAAoB,CAACmB,WAAWL,2BAAgB,CAACC,MAAM,CAACC,YAAY,EAAE;YAC5H1B,OAAOjC,yBAAyBE,oBAAoB,EAAEyC,oBAAoB,CAACmB;YAC3E7B,OAAOY,cAAcF,oBAAoB,CAACsB,YAAmBF,SAAS/C;QACxE;QAEAgB,GAAG,iDAAiD;YAClDnC,WAAWC,SAAS,GAAG;YACvB,MAAM+C,eAAe/D,KAAK+B,KAAK,CAACnB,SAAS,yBAAyByB,iBAAiB,CAACH;YACpF,MAAMtB,QAAQS,MAAM,CAAC;gBAAC;aAAE,EAAE;gBAAEiD,KAAKC,+BAAgB,CAACa,IAAI;YAAC,GAAU;gBAAEX,QAAQ;oBAAEf,IAAI;oBAAIgB,OAAO;gBAAO;YAAE;YACrGvB,OAAOjC,yBAAyBG,MAAM,EAAEwC,oBAAoB,CAAC,IAAI;gBAAC;aAAE,EAAE;gBAAES,KAAKC,+BAAgB,CAACa,IAAI;YAAC;YACnGjC,OAAO3B,2BAA2BC,kBAAkB,EAAEoC,oBAAoB,CAAC;gBAAC;aAAE,EAAEc,2BAAgB,CAACC,MAAM,CAACC,YAAY,EAAE;YACtH1B,OAAOjC,yBAAyBE,oBAAoB,EAAE0D,GAAG,CAACC,gBAAgB;YAC1E5B,OAAOY,cAAce,GAAG,CAACC,gBAAgB;QAC3C;QAEA7B,GAAG,6EAA6E;YAC9E,MAAMmC,YAAYvD;YAClBZ,yBAAyBG,MAAM,CAACiE,qBAAqB,CAAC,IAAIC,MAAM;YAChE,MAAM3E,QAAQS,MAAM,CAAC;gBAAC;aAAE,EAAE;gBAAEiD,KAAKC,+BAAgB,CAACiB,KAAK;YAAC;YACxD,MAAM9D;YACNyB,OAAOkC,WAAWN,gBAAgB;YAClC5B,OAAOkC,UAAUpF,IAAI,CAACwF,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,EAAYC,OAAO,CAAC;QACzD;QAEAxC,GAAG,oEAAoE;YACrE,MAAMmC,YAAYvD;YAClB9B,KAAK+B,KAAK,CAAWnB,SAAgB,qBAAqB0E,qBAAqB,CAAC,IAAIC,MAAM;YAC1F,MAAM3E,QAAQS,MAAM,CAAC;gBAAC;gBAAG;aAAE,EAAE;gBAAEiD,KAAKC,+BAAgB,CAACa,IAAI;YAAC,GAAU;gBAAEX,QAAQ;oBAAEf,IAAI;oBAAGgB,OAAO;gBAAK;YAAE;YACrG,MAAMhD;YACNyB,OAAOkC,WAAWN,gBAAgB;YAClC5B,OAAOkC,UAAUpF,IAAI,CAACwF,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,EAAYC,OAAO,CAAC;QACzD;QAEAxC,GAAG,gEAAgE;YACjE,MAAMmC,YAAYvD;YAClBZ,yBAAyBE,oBAAoB,CAACuC,qBAAqB,CAAC;gBAAC;oBAAED,IAAI;oBAAGO,OAAO;oBAAUC,UAAU;gBAAK;aAAE;YAChHlE,KAAK+B,KAAK,CAACnB,SAAS,yBAAyB0E,qBAAqB,CAAC,IAAIC,MAAM;YAC7E,MAAM3E,QAAQS,MAAM,CAAC;gBAAC;aAAE,EAAE;gBAAEiD,KAAKC,+BAAgB,CAACC,QAAQ;YAAC;YAC3D,MAAM9C;YACNyB,OAAOkC,WAAWN,gBAAgB;YAClC5B,OAAOkC,UAAUpF,IAAI,CAACwF,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,EAAYC,OAAO,CAAC;QACzD;IACF;IAEAjF,SAAS,WAAW;QAClByC,GAAG,2CAA2C;YAC5CtC,QAAQU,OAAO,CAAC;gBAAEoC,IAAI;YAAE,GAAU;YAClCP,OAAOjC,yBAAyBI,OAAO,EAAEuC,oBAAoB,CAAC,GAAG;YACjE,MAAMwB,YAAYvD;YAClBZ,yBAAyBI,OAAO,CAACgE,qBAAqB,CAAC,IAAIC,MAAM;YACjE3E,QAAQU,OAAO,CAAC;gBAAEoC,IAAI;YAAE,GAAUxB;YAClC,MAAMR;YACNyB,OAAOkC,WAAWN,gBAAgB;YAClC5B,OAAOkC,UAAUpF,IAAI,CAACwF,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,EAAYC,OAAO,CAAC;QACzD;IACF;IAEAjF,SAAS,UAAU;QACjByC,GAAG,8BAA8B;YAC/B,MAAMtC,QAAQW,MAAM,CAAC;gBAAEmC,IAAI;YAAG,GAAU;YACxCP,OAAOjC,yBAAyBK,MAAM,EAAEsC,oBAAoB,CAAC,IAAI;QACnE;IACF;IAEApD,SAAS,yBAAyB;QAChCyC,GAAG,8CAA8C;YAC/CnC,WAAWC,SAAS,GAAG;YACvB,MAAMJ,QAAQ+E,qBAAqB,CACjC;gBAAC;oBAAEjC,IAAI;oBAAGO,OAAO;oBAAUC,UAAU;gBAAK;aAAE,EAC5C;gBAAEI,KAAKC,+BAAgB,CAACC,QAAQ;YAAC,GACjC;gBACEC,QAAQ;oBAAEf,IAAI;oBAAGgB,OAAO;gBAAO;YACjC;YAEFvB,OAAOtC,iBAAiBC,eAAe,EAAEgE,GAAG,CAACC,gBAAgB;YAC7D5B,OAAOpC,WAAWE,SAAS,EAAE6D,GAAG,CAACC,gBAAgB;QACnD;QAEA7B,GAAG,iDAAiD;YAClDrC,iBAAiBC,eAAe,CAAC6C,qBAAqB,CAAC;YACvD,MAAMK,UAAU;gBACd;oBAAEN,IAAI;oBAAGO,OAAO;oBAAUC,UAAU;gBAAK;gBACzC;oBAAER,IAAI;oBAAGO,OAAO;oBAAUC,UAAU;gBAAK;aAC1C;YACD,MAAM0B,UAAe;gBAAEnB,QAAQ;oBAAEf,IAAI;oBAAGgB,OAAO;gBAAO;gBAAGO,SAAS;gBAASY,YAAY;YAAwB;YAC/G,MAAMZ,UAAU;gBAAEX,KAAKC,+BAAgB,CAACC,QAAQ;YAAC;YACjD,MAAM5D,QAAQ+E,qBAAqB,CAAC3B,SAAgBiB,SAASW;YAC7DzC,OAAOtC,iBAAiBC,eAAe,EAAE+C,oBAAoB,CAAC;YAC9DV,OAAOyC,QAAQnB,MAAM,CAACqB,YAAY,EAAEhC,IAAI,CAAC;YACzCX,OAAOpC,WAAWE,SAAS,EAAE8E,qBAAqB,CAAC;YACnD5C,OAAO,AAACpC,WAAWE,SAAS,CAAehB,IAAI,CAACwF,KAAK,CAAC,EAAE,CAAC,EAAE,EAAEO,OAAO,CAAC;gBACnE;oBAAEC,IAAI;oBAAUC,SAAS;oBAAiBC,MAAM;gBAAe;gBAC/D;oBAAEF,IAAI;oBAAUC,SAAS;oBAAiBC,MAAM;gBAAe;aAChE;QACH;QAEAjD,GAAG,qCAAqC;YACtCnC,WAAWE,SAAS,CAACqE,qBAAqB,CAAC,IAAIC,MAAM;YACrD,MAAMF,YAAYvD;YAClB,MAAMlB,QAAQ+E,qBAAqB,CAAC;gBAAC;oBAAEjC,IAAI;oBAAGO,OAAO;oBAAUC,UAAU;gBAAK;aAAE,EAAS;gBAAEI,KAAKC,+BAAgB,CAACa,IAAI;YAAC,GAAU,CAAC;YACjIjC,OAAOkC,WAAWN,gBAAgB;YAClC5B,OAAOkC,UAAUpF,IAAI,CAACwF,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,EAAYC,OAAO,CAAC;QACzD;IACF;IAEAjF,SAAS,uCAAuC;QAC9C,MAAM2F,QAAQ;YACZ;gBACEzF,MAAM;gBACN2D,KAAKC,+BAAgB,CAACC,QAAQ;gBAC9BrE,IAAI;gBACJyF,SAAS;oBAAEX,SAAS;oBAAKY,YAAY;oBAAKpB,QAAQ;wBAAEf,IAAI;wBAAGgB,OAAO;oBAAI;gBAAE;YAC1E;YACA;gBAAE/D,MAAM;gBAAU2D,KAAKC,+BAAgB,CAAC8B,MAAM;gBAAElG,IAAI;gBAAayF,SAAS;oBAAEC,YAAY;oBAAKS,QAAQ;gBAAI;YAAE;YAC3G;gBACE3F,MAAM;gBACN2D,KAAKC,+BAAgB,CAACgC,WAAW;gBACjCpG,IAAI;gBACJyF,SAAS;oBAAEC,YAAY;oBAAKpB,QAAQ;wBAAEf,IAAI;wBAAGgB,OAAO;oBAAI;oBAAG4B,QAAQ;gBAAI;YACzE;YACA;gBAAE3F,MAAM;gBAAU2D,KAAKC,+BAAgB,CAACW,MAAM;gBAAE/E,IAAI;gBAAayF,SAAS;oBAAEC,YAAY;oBAAKpB,QAAQ;wBAAEf,IAAI;wBAAGgB,OAAO;oBAAI;oBAAG4B,QAAQ;gBAAI;YAAE;YAC1I;gBACE3F,MAAM;gBACN2D,KAAKC,+BAAgB,CAACiB,KAAK;gBAC3BrF,IAAI;gBACJyF,SAAS;oBAAEC,YAAY;oBAAKpB,QAAQ;wBAAEf,IAAI;wBAAGgB,OAAO;oBAAI;oBAAG8B,UAAU;oBAAQF,QAAQ;gBAAI;YAC3F;YACA;gBAAE3F,MAAM;gBAAQ2D,KAAKC,+BAAgB,CAACa,IAAI;gBAAEjF,IAAI;gBAAYyF,SAAS;oBAAEC,YAAY;oBAAKS,QAAQ;gBAAI;YAAE;SACvG;QAEDpD,GAAGG,IAAI,CAAC+C,OAAO,sBAAsB,CAAC,EAAE9B,GAAG,EAAEnE,EAAE,EAAEyF,OAAO,EAAE;YACxD,MAAMhC,MAAM,AAAChD,QAAgB6F,OAAO,CAAC,MAAM;gBAAEnC;YAAI,GAAUsB;YAC3DzC,OAAOS,KAAKoC,OAAO,CAAC;gBAClB,GAAG7F,GAAGuG,OAAO,CAAC,QAAQ,IAAI,MAAM,CAAC,CAACA,OAAO,CAAC,aAAa;gBACvD,GAAGvG,GAAGuG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC,CAACA,OAAO,CAAC,aAAa;aACvD;YACDvD,OAAO,AAACwD,OAAkB,CAACxG,GAAG,EAAE4E,gBAAgB;QAClD;QAEA7B,GAAG,gCAAgC;YACjC,MAAMmC,YAAYvD;YAClB,MAAM8E,SAAS,AAAChG,QAAgB6F,OAAO,CAAC,MAAM;gBAAEnC,KAAK;YAAM,GAAU,CAAC;YACtEnB,OAAOyD,QAAQC,aAAa;YAC5B1D,OAAOkC,WAAWN,gBAAgB;YAClC5B,OAAOkC,UAAUpF,IAAI,CAACwF,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,EAAYC,OAAO,CAAC;QACzD;IACF;AACF"}