@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
@@ -6,19 +6,65 @@
6
6
  Object.defineProperty(exports, "__esModule", {
7
7
  value: true
8
8
  });
9
+ const _common = require("@nestjs/common");
9
10
  const _testing = require("@nestjs/testing");
10
11
  const _cacheservice = require("../../../infrastructure/cache/services/cache.service");
11
12
  const _contextmanagerservice = require("../../../infrastructure/context/services/context-manager.service");
12
13
  const _constants = require("../../../infrastructure/database/constants");
13
14
  const _filesqueriesservice = require("../../files/services/files-queries.service");
15
+ const _files = require("../../files/utils/files");
14
16
  const _notificationsmanagerservice = require("../../notifications/services/notifications-manager.service");
15
17
  const _sharesqueriesservice = require("../../shares/services/shares-queries.service");
16
18
  const _spacesqueriesservice = require("../../spaces/services/spaces-queries.service");
17
19
  const _commentsmanagerservice = require("./comments-manager.service");
18
20
  const _commentsqueriesservice = require("./comments-queries.service");
21
+ // Mocks of the file utilities used by the service
22
+ jest.mock('../../files/utils/files', ()=>({
23
+ isPathExists: jest.fn(),
24
+ getProps: jest.fn(),
25
+ dirName: jest.fn(),
26
+ fileName: jest.fn()
27
+ }));
19
28
  describe(_commentsmanagerservice.CommentsManager.name, ()=>{
20
- let service;
29
+ let commentsManager;
30
+ let contextManager;
31
+ let commentQueries;
32
+ let filesQueries;
33
+ let notificationsManager;
34
+ const user = {
35
+ id: 42,
36
+ email: 'john@doe.tld'
37
+ };
38
+ const makeSpace = (overrides = {})=>({
39
+ realPath: '/real/path',
40
+ url: '/space/folder/file.txt',
41
+ dbFile: {
42
+ path: 'folder',
43
+ ownerId: 42,
44
+ spaceExternalRootId: null,
45
+ shareExternalId: null
46
+ },
47
+ ...overrides
48
+ });
21
49
  beforeAll(async ()=>{
50
+ commentQueries = {
51
+ getComments: jest.fn(),
52
+ createComment: jest.fn(),
53
+ updateComment: jest.fn(),
54
+ deleteComment: jest.fn(),
55
+ getRecentsFromUser: jest.fn(),
56
+ membersToNotify: jest.fn()
57
+ };
58
+ filesQueries = {
59
+ getSpaceFileId: jest.fn(),
60
+ getOrCreateSpaceFile: jest.fn()
61
+ };
62
+ notificationsManager = {
63
+ create: jest.fn().mockResolvedValue(undefined)
64
+ };
65
+ contextManager = {
66
+ get: jest.fn().mockReturnValue('https://app.local/path')
67
+ };
22
68
  const module = await _testing.Test.createTestingModule({
23
69
  providers: [
24
70
  {
@@ -31,20 +77,374 @@ describe(_commentsmanagerservice.CommentsManager.name, ()=>{
31
77
  },
32
78
  {
33
79
  provide: _notificationsmanagerservice.NotificationsManager,
80
+ useValue: notificationsManager
81
+ },
82
+ {
83
+ provide: _contextmanagerservice.ContextManager,
84
+ useValue: contextManager
85
+ },
86
+ {
87
+ provide: _commentsmanagerservice.CommentsManager,
88
+ useClass: _commentsmanagerservice.CommentsManager
89
+ },
90
+ {
91
+ provide: _commentsqueriesservice.CommentsQueries,
92
+ useValue: commentQueries
93
+ },
94
+ {
95
+ provide: _filesqueriesservice.FilesQueries,
96
+ useValue: filesQueries
97
+ },
98
+ {
99
+ provide: _spacesqueriesservice.SpacesQueries,
34
100
  useValue: {}
35
101
  },
36
- _contextmanagerservice.ContextManager,
37
- _commentsmanagerservice.CommentsManager,
38
- _commentsqueriesservice.CommentsQueries,
39
- _filesqueriesservice.FilesQueries,
40
- _spacesqueriesservice.SpacesQueries,
41
- _sharesqueriesservice.SharesQueries
102
+ {
103
+ provide: _sharesqueriesservice.SharesQueries,
104
+ useValue: {}
105
+ }
42
106
  ]
43
107
  }).compile();
44
- service = module.get(_commentsmanagerservice.CommentsManager);
108
+ commentsManager = module.get(_commentsmanagerservice.CommentsManager);
109
+ });
110
+ beforeEach(()=>{
111
+ jest.clearAllMocks();
112
+ _files.isPathExists.mockResolvedValue(true);
113
+ _files.getProps.mockResolvedValue({
114
+ name: 'file.txt',
115
+ path: 'folder'
116
+ });
117
+ _files.dirName.mockReturnValue('/space/folder');
118
+ _files.fileName.mockReturnValue('file.txt');
45
119
  });
46
120
  it('should be defined', ()=>{
47
- expect(service).toBeDefined();
121
+ expect(commentsManager).toBeDefined();
122
+ });
123
+ describe('getComments', ()=>{
124
+ it('returns [] if no fileId', async ()=>{
125
+ filesQueries.getSpaceFileId.mockResolvedValue(0);
126
+ const res = await commentsManager.getComments(user, makeSpace());
127
+ expect(res).toEqual([]);
128
+ expect(filesQueries.getSpaceFileId).toHaveBeenCalledTimes(1);
129
+ expect(commentQueries.getComments).not.toHaveBeenCalled();
130
+ });
131
+ it('returns comments if fileId is valid', async ()=>{
132
+ filesQueries.getSpaceFileId.mockResolvedValue(123);
133
+ const expected = [
134
+ {
135
+ id: 1
136
+ },
137
+ {
138
+ id: 2
139
+ }
140
+ ];
141
+ commentQueries.getComments.mockResolvedValue(expected);
142
+ const res = await commentsManager.getComments(user, makeSpace());
143
+ expect(filesQueries.getSpaceFileId).toHaveBeenCalled();
144
+ expect(commentQueries.getComments).toHaveBeenCalledWith(42, true, 123);
145
+ expect(res).toBe(expected);
146
+ });
147
+ it('throws NOT_FOUND if path does not exist', async ()=>{
148
+ ;
149
+ _files.isPathExists.mockResolvedValue(false);
150
+ await expect(commentsManager.getComments(user, makeSpace())).rejects.toThrow(_common.HttpException);
151
+ await expect(commentsManager.getComments(user, makeSpace())).rejects.toMatchObject({
152
+ status: _common.HttpStatus.NOT_FOUND
153
+ });
154
+ });
155
+ });
156
+ describe('createComment', ()=>{
157
+ it("rejects on external root/share at path '.'", async ()=>{
158
+ const space = makeSpace({
159
+ dbFile: {
160
+ path: '.',
161
+ ownerId: 42,
162
+ spaceExternalRootId: 'ext',
163
+ shareExternalId: null
164
+ }
165
+ });
166
+ await expect(commentsManager.createComment(user, space, {
167
+ fileId: 0,
168
+ content: 'Hi'
169
+ })).rejects.toMatchObject({
170
+ status: _common.HttpStatus.BAD_REQUEST
171
+ });
172
+ const space2 = makeSpace({
173
+ dbFile: {
174
+ path: '.',
175
+ ownerId: 42,
176
+ spaceExternalRootId: null,
177
+ shareExternalId: 'shr'
178
+ }
179
+ });
180
+ await expect(commentsManager.createComment(user, space2, {
181
+ fileId: 0,
182
+ content: 'Hi'
183
+ })).rejects.toMatchObject({
184
+ status: _common.HttpStatus.BAD_REQUEST
185
+ });
186
+ });
187
+ it('rejects BAD_REQUEST if provided fileId mismatches', async ()=>{
188
+ filesQueries.getSpaceFileId.mockResolvedValue(100);
189
+ await expect(commentsManager.createComment(user, makeSpace(), {
190
+ fileId: 101,
191
+ content: 'x'
192
+ })).rejects.toMatchObject({
193
+ status: _common.HttpStatus.BAD_REQUEST
194
+ });
195
+ });
196
+ it('uses getOrCreate when fileId < 0', async ()=>{
197
+ filesQueries.getOrCreateSpaceFile.mockResolvedValue(555);
198
+ commentQueries.createComment.mockResolvedValue(777);
199
+ commentQueries.getComments.mockResolvedValue([
200
+ {
201
+ id: 777,
202
+ fileId: 555,
203
+ content: 'hello'
204
+ }
205
+ ]);
206
+ // Force a rejection in notify() to cover the catch attached to this.notify(...) in createComment
207
+ commentQueries.membersToNotify.mockRejectedValueOnce(new Error('members failed'));
208
+ const loggerSpy = jest.spyOn(commentsManager['logger'], 'error').mockImplementation(()=>undefined);
209
+ const res = await commentsManager.createComment(user, makeSpace(), {
210
+ fileId: -1,
211
+ content: 'hello'
212
+ });
213
+ // Let the microtask run the catch of createComment
214
+ await new Promise((r)=>setImmediate(r));
215
+ expect(filesQueries.getOrCreateSpaceFile).toHaveBeenCalled();
216
+ expect(filesQueries.getSpaceFileId).not.toHaveBeenCalled();
217
+ expect(commentQueries.createComment).toHaveBeenCalledWith(42, 555, 'hello');
218
+ expect(notificationsManager.create).not.toHaveBeenCalled();
219
+ expect(res).toEqual({
220
+ id: 777,
221
+ fileId: 555,
222
+ content: 'hello'
223
+ });
224
+ // Verify that the catch of createComment logged the error
225
+ expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('createComment'));
226
+ loggerSpy.mockRestore();
227
+ });
228
+ it('notifies members when present', async ()=>{
229
+ filesQueries.getSpaceFileId.mockResolvedValue(10);
230
+ commentQueries.createComment.mockResolvedValue(1);
231
+ commentQueries.getComments.mockResolvedValue([
232
+ {
233
+ id: 1,
234
+ fileId: 10,
235
+ content: 'c'
236
+ }
237
+ ]);
238
+ commentQueries.membersToNotify.mockResolvedValue([
239
+ {
240
+ id: 2,
241
+ email: 'a@b.c'
242
+ }
243
+ ]);
244
+ // Force rejection of notification creation to trigger the catch in notify()
245
+ notificationsManager.create.mockRejectedValueOnce(new Error('notify failed'));
246
+ const loggerSpy = jest.spyOn(commentsManager['logger'], 'error').mockImplementation(()=>undefined);
247
+ await commentsManager.createComment(user, makeSpace(), {
248
+ fileId: 10,
249
+ content: 'c'
250
+ });
251
+ // Let the microtask execute the internal catch of notify()
252
+ await new Promise((r)=>setImmediate(r));
253
+ expect(notificationsManager.create).toHaveBeenCalledTimes(1);
254
+ notificationsManager.create.mockClear();
255
+ expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('notify'));
256
+ loggerSpy.mockRestore();
257
+ const space = makeSpace();
258
+ await commentsManager.createComment(user, space, {
259
+ fileId: 10,
260
+ content: 'c'
261
+ });
262
+ expect(commentQueries.membersToNotify).toHaveBeenCalledWith(42, 10);
263
+ expect(notificationsManager.create).toHaveBeenCalledTimes(1);
264
+ const [members, notification, data] = notificationsManager.create.mock.calls[0];
265
+ expect(members).toEqual([
266
+ {
267
+ id: 2,
268
+ email: 'a@b.c'
269
+ }
270
+ ]);
271
+ expect(notification).toMatchObject({
272
+ app: expect.anything(),
273
+ event: expect.anything(),
274
+ element: 'file.txt',
275
+ url: '/space/folder'
276
+ });
277
+ expect(_files.fileName).toHaveBeenCalledWith(space.url);
278
+ expect(_files.dirName).toHaveBeenCalledWith(space.url);
279
+ expect(data).toMatchObject({
280
+ author: user,
281
+ currentUrl: 'https://app.local/path',
282
+ content: 'c'
283
+ });
284
+ });
285
+ it('logs an error if notificationsManager.create rejects (covers catch in notify)', async ()=>{
286
+ filesQueries.getSpaceFileId.mockResolvedValue(10);
287
+ commentQueries.createComment.mockResolvedValue(1);
288
+ commentQueries.getComments.mockResolvedValue([
289
+ {
290
+ id: 1,
291
+ fileId: 10,
292
+ content: 'c'
293
+ }
294
+ ]);
295
+ commentQueries.membersToNotify.mockResolvedValue([
296
+ {
297
+ id: 2,
298
+ email: 'a@b.c'
299
+ }
300
+ ]);
301
+ // Force rejection to trigger the catch in notify()
302
+ notificationsManager.create.mockRejectedValueOnce(new Error('notify failed'));
303
+ const loggerSpy = jest.spyOn(commentsManager['logger'], 'error').mockImplementation(()=>undefined);
304
+ await commentsManager.createComment(user, makeSpace(), {
305
+ fileId: 10,
306
+ content: 'c'
307
+ });
308
+ // Allow the microtask to run the internal catch of notify()
309
+ await new Promise((r)=>setImmediate(r));
310
+ expect(notificationsManager.create).toHaveBeenCalledTimes(1);
311
+ expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('notify'));
312
+ loggerSpy.mockRestore();
313
+ });
314
+ it('does not notify if no members', async ()=>{
315
+ filesQueries.getSpaceFileId.mockResolvedValue(10);
316
+ commentQueries.createComment.mockResolvedValue(1);
317
+ commentQueries.getComments.mockResolvedValue([
318
+ {
319
+ id: 1
320
+ }
321
+ ]);
322
+ commentQueries.membersToNotify.mockResolvedValue([]);
323
+ await commentsManager.createComment(user, makeSpace(), {
324
+ fileId: 10,
325
+ content: 'c'
326
+ });
327
+ expect(notificationsManager.create).not.toHaveBeenCalled();
328
+ });
329
+ });
330
+ describe('updateComment', ()=>{
331
+ it('rejects NOT_FOUND if target comment is not found', async ()=>{
332
+ commentQueries.getComments.mockResolvedValue([]);
333
+ await expect(commentsManager.updateComment(user, makeSpace(), {
334
+ commentId: 99,
335
+ fileId: 1,
336
+ content: 'z'
337
+ })).rejects.toMatchObject({
338
+ status: _common.HttpStatus.NOT_FOUND
339
+ });
340
+ });
341
+ it('rejects BAD_REQUEST if fileId mismatches', async ()=>{
342
+ commentQueries.getComments.mockResolvedValue([
343
+ {
344
+ id: 50,
345
+ fileId: 123
346
+ }
347
+ ]);
348
+ filesQueries.getSpaceFileId.mockResolvedValue(999);
349
+ await expect(commentsManager.updateComment(user, makeSpace(), {
350
+ commentId: 50,
351
+ fileId: 123,
352
+ content: 'z'
353
+ })).rejects.toMatchObject({
354
+ status: _common.HttpStatus.BAD_REQUEST
355
+ });
356
+ });
357
+ it('rejects INTERNAL_SERVER_ERROR if update fails', async ()=>{
358
+ commentQueries.getComments.mockResolvedValueOnce([
359
+ {
360
+ id: 50,
361
+ fileId: 5
362
+ }
363
+ ]);
364
+ filesQueries.getSpaceFileId.mockResolvedValue(5);
365
+ commentQueries.updateComment.mockResolvedValue(false);
366
+ await expect(commentsManager.updateComment(user, makeSpace(), {
367
+ commentId: 50,
368
+ fileId: 5,
369
+ content: 'z'
370
+ })).rejects.toMatchObject({
371
+ status: _common.HttpStatus.INTERNAL_SERVER_ERROR
372
+ });
373
+ });
374
+ it('returns the comment after update', async ()=>{
375
+ commentQueries.getComments.mockResolvedValueOnce([
376
+ {
377
+ id: 50,
378
+ fileId: 5
379
+ }
380
+ ]) // initial fetch
381
+ .mockResolvedValueOnce([
382
+ {
383
+ id: 50,
384
+ fileId: 5,
385
+ content: 'updated'
386
+ }
387
+ ]); // fetch after update -> include content
388
+ filesQueries.getSpaceFileId.mockResolvedValue(5);
389
+ commentQueries.updateComment.mockResolvedValue(true);
390
+ const res = await commentsManager.updateComment(user, makeSpace(), {
391
+ commentId: 50,
392
+ fileId: 5,
393
+ content: 'updated'
394
+ });
395
+ expect(commentQueries.updateComment).toHaveBeenCalledWith(42, 50, 5, 'updated');
396
+ // allow additional fields via toMatchObject
397
+ expect(res).toMatchObject({
398
+ id: 50,
399
+ fileId: 5,
400
+ content: 'updated'
401
+ });
402
+ });
403
+ });
404
+ describe('deleteComment', ()=>{
405
+ it('rejects BAD_REQUEST if fileId mismatches', async ()=>{
406
+ filesQueries.getSpaceFileId.mockResolvedValue(10);
407
+ await expect(commentsManager.deleteComment(user, makeSpace(), {
408
+ commentId: 1,
409
+ fileId: 11
410
+ })).rejects.toMatchObject({
411
+ status: _common.HttpStatus.BAD_REQUEST
412
+ });
413
+ });
414
+ it('rejects FORBIDDEN if deletion is denied', async ()=>{
415
+ filesQueries.getSpaceFileId.mockResolvedValue(10);
416
+ commentQueries.deleteComment.mockResolvedValue(false);
417
+ await expect(commentsManager.deleteComment(user, makeSpace(), {
418
+ commentId: 1,
419
+ fileId: 10
420
+ })).rejects.toMatchObject({
421
+ status: _common.HttpStatus.FORBIDDEN
422
+ });
423
+ });
424
+ it('resolves when deletion succeeds', async ()=>{
425
+ filesQueries.getSpaceFileId.mockResolvedValue(10);
426
+ commentQueries.deleteComment.mockResolvedValue(true);
427
+ await expect(commentsManager.deleteComment(user, makeSpace(), {
428
+ commentId: 1,
429
+ fileId: 10
430
+ })).resolves.toBeUndefined();
431
+ });
432
+ });
433
+ describe('getRecents', ()=>{
434
+ it('delegates to commentQueries.getRecentsFromUser', async ()=>{
435
+ const recents = [
436
+ {
437
+ id: 1
438
+ },
439
+ {
440
+ id: 2
441
+ }
442
+ ];
443
+ commentQueries.getRecentsFromUser.mockResolvedValue(recents);
444
+ const res = await commentsManager.getRecents(user, 5);
445
+ expect(commentQueries.getRecentsFromUser).toHaveBeenCalledWith(user, 5);
446
+ expect(res).toBe(recents);
447
+ });
48
448
  });
49
449
  });
50
450
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../backend/src/applications/comments/services/comments-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 { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { ContextManager } from '../../../infrastructure/context/services/context-manager.service'\nimport { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants'\nimport { FilesQueries } from '../../files/services/files-queries.service'\nimport { NotificationsManager } from '../../notifications/services/notifications-manager.service'\nimport { SharesQueries } from '../../shares/services/shares-queries.service'\nimport { SpacesQueries } from '../../spaces/services/spaces-queries.service'\nimport { CommentsManager } from './comments-manager.service'\nimport { CommentsQueries } from './comments-queries.service'\n\ndescribe(CommentsManager.name, () => {\n let service: CommentsManager\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n {\n provide: DB_TOKEN_PROVIDER,\n useValue: {}\n },\n { provide: Cache, useValue: {} },\n { provide: NotificationsManager, useValue: {} },\n ContextManager,\n CommentsManager,\n CommentsQueries,\n FilesQueries,\n SpacesQueries,\n SharesQueries\n ]\n }).compile()\n\n service = module.get<CommentsManager>(CommentsManager)\n })\n\n it('should be defined', () => {\n expect(service).toBeDefined()\n })\n})\n"],"names":["describe","CommentsManager","name","service","beforeAll","module","Test","createTestingModule","providers","provide","DB_TOKEN_PROVIDER","useValue","Cache","NotificationsManager","ContextManager","CommentsQueries","FilesQueries","SpacesQueries","SharesQueries","compile","get","it","expect","toBeDefined"],"mappings":"AAAA;;;;CAIC;;;;yBAEmC;8BACd;uCACS;2BACG;qCACL;6CACQ;sCACP;sCACA;wCACE;wCACA;AAEhCA,SAASC,uCAAe,CAACC,IAAI,EAAE;IAC7B,IAAIC;IAEJC,UAAU;QACR,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,WAAW;gBACT;oBACEC,SAASC,4BAAiB;oBAC1BC,UAAU,CAAC;gBACb;gBACA;oBAAEF,SAASG,mBAAK;oBAAED,UAAU,CAAC;gBAAE;gBAC/B;oBAAEF,SAASI,iDAAoB;oBAAEF,UAAU,CAAC;gBAAE;gBAC9CG,qCAAc;gBACdb,uCAAe;gBACfc,uCAAe;gBACfC,iCAAY;gBACZC,mCAAa;gBACbC,mCAAa;aACd;QACH,GAAGC,OAAO;QAEVhB,UAAUE,OAAOe,GAAG,CAAkBnB,uCAAe;IACvD;IAEAoB,GAAG,qBAAqB;QACtBC,OAAOnB,SAASoB,WAAW;IAC7B;AACF"}
1
+ {"version":3,"sources":["../../../../../backend/src/applications/comments/services/comments-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 { HttpException, HttpStatus } from '@nestjs/common'\nimport { Test, TestingModule } from '@nestjs/testing'\nimport { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { ContextManager } from '../../../infrastructure/context/services/context-manager.service'\nimport { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants'\nimport { FilesQueries } from '../../files/services/files-queries.service'\nimport { dirName, fileName, getProps, isPathExists } from '../../files/utils/files'\nimport { NotificationsManager } from '../../notifications/services/notifications-manager.service'\nimport { SharesQueries } from '../../shares/services/shares-queries.service'\nimport { SpacesQueries } from '../../spaces/services/spaces-queries.service'\nimport { CommentsManager } from './comments-manager.service'\nimport { CommentsQueries } from './comments-queries.service'\n\n// Mocks of the file utilities used by the service\njest.mock('../../files/utils/files', () => ({\n isPathExists: jest.fn(),\n getProps: jest.fn(),\n dirName: jest.fn(),\n fileName: jest.fn()\n}))\n\ndescribe(CommentsManager.name, () => {\n let commentsManager: CommentsManager\n let contextManager: { get: jest.Mock }\n let commentQueries: {\n getComments: jest.Mock\n createComment: jest.Mock\n updateComment: jest.Mock\n deleteComment: jest.Mock\n getRecentsFromUser: jest.Mock\n membersToNotify: jest.Mock\n }\n let filesQueries: {\n getSpaceFileId: jest.Mock\n getOrCreateSpaceFile: jest.Mock\n }\n let notificationsManager: { create: jest.Mock }\n\n const user = { id: 42, email: 'john@doe.tld' } as any\n\n const makeSpace = (overrides: Partial<any> = {}) =>\n ({\n realPath: '/real/path',\n url: '/space/folder/file.txt',\n dbFile: {\n path: 'folder',\n ownerId: 42,\n spaceExternalRootId: null,\n shareExternalId: null\n },\n ...overrides\n }) as any\n\n beforeAll(async () => {\n commentQueries = {\n getComments: jest.fn(),\n createComment: jest.fn(),\n updateComment: jest.fn(),\n deleteComment: jest.fn(),\n getRecentsFromUser: jest.fn(),\n membersToNotify: jest.fn()\n }\n filesQueries = {\n getSpaceFileId: jest.fn(),\n getOrCreateSpaceFile: jest.fn()\n }\n notificationsManager = {\n create: jest.fn().mockResolvedValue(undefined)\n }\n contextManager = {\n get: jest.fn().mockReturnValue('https://app.local/path')\n }\n\n const module: TestingModule = await Test.createTestingModule({\n providers: [\n {\n provide: DB_TOKEN_PROVIDER,\n useValue: {}\n },\n { provide: Cache, useValue: {} },\n { provide: NotificationsManager, useValue: notificationsManager },\n { provide: ContextManager, useValue: contextManager },\n { provide: CommentsManager, useClass: CommentsManager },\n { provide: CommentsQueries, useValue: commentQueries },\n { provide: FilesQueries, useValue: filesQueries },\n { provide: SpacesQueries, useValue: {} },\n { provide: SharesQueries, useValue: {} }\n ]\n }).compile()\n\n commentsManager = module.get<CommentsManager>(CommentsManager)\n })\n\n beforeEach(() => {\n jest.clearAllMocks()\n ;(isPathExists as jest.Mock).mockResolvedValue(true)\n ;(getProps as jest.Mock).mockResolvedValue({ name: 'file.txt', path: 'folder' })\n ;(dirName as jest.Mock).mockReturnValue('/space/folder')\n ;(fileName as jest.Mock).mockReturnValue('file.txt')\n })\n\n it('should be defined', () => {\n expect(commentsManager).toBeDefined()\n })\n\n describe('getComments', () => {\n it('returns [] if no fileId', async () => {\n filesQueries.getSpaceFileId.mockResolvedValue(0)\n\n const res = await commentsManager.getComments(user, makeSpace())\n\n expect(res).toEqual([])\n expect(filesQueries.getSpaceFileId).toHaveBeenCalledTimes(1)\n expect(commentQueries.getComments).not.toHaveBeenCalled()\n })\n\n it('returns comments if fileId is valid', async () => {\n filesQueries.getSpaceFileId.mockResolvedValue(123)\n const expected = [{ id: 1 }, { id: 2 }]\n commentQueries.getComments.mockResolvedValue(expected)\n\n const res = await commentsManager.getComments(user, makeSpace())\n\n expect(filesQueries.getSpaceFileId).toHaveBeenCalled()\n expect(commentQueries.getComments).toHaveBeenCalledWith(42, true, 123)\n expect(res).toBe(expected)\n })\n\n it('throws NOT_FOUND if path does not exist', async () => {\n ;(isPathExists as jest.Mock).mockResolvedValue(false)\n\n await expect(commentsManager.getComments(user, makeSpace())).rejects.toThrow(HttpException)\n await expect(commentsManager.getComments(user, makeSpace())).rejects.toMatchObject({ status: HttpStatus.NOT_FOUND })\n })\n })\n\n describe('createComment', () => {\n it(\"rejects on external root/share at path '.'\", async () => {\n const space = makeSpace({\n dbFile: { path: '.', ownerId: 42, spaceExternalRootId: 'ext', shareExternalId: null }\n })\n await expect(commentsManager.createComment(user, space, { fileId: 0, content: 'Hi' } as any)).rejects.toMatchObject({\n status: HttpStatus.BAD_REQUEST\n })\n\n const space2 = makeSpace({\n dbFile: { path: '.', ownerId: 42, spaceExternalRootId: null, shareExternalId: 'shr' }\n })\n await expect(commentsManager.createComment(user, space2, { fileId: 0, content: 'Hi' } as any)).rejects.toMatchObject({\n status: HttpStatus.BAD_REQUEST\n })\n })\n\n it('rejects BAD_REQUEST if provided fileId mismatches', async () => {\n filesQueries.getSpaceFileId.mockResolvedValue(100)\n\n await expect(commentsManager.createComment(user, makeSpace(), { fileId: 101, content: 'x' } as any)).rejects.toMatchObject({\n status: HttpStatus.BAD_REQUEST\n })\n })\n\n it('uses getOrCreate when fileId < 0', async () => {\n filesQueries.getOrCreateSpaceFile.mockResolvedValue(555)\n commentQueries.createComment.mockResolvedValue(777)\n commentQueries.getComments.mockResolvedValue([{ id: 777, fileId: 555, content: 'hello' }])\n // Force a rejection in notify() to cover the catch attached to this.notify(...) in createComment\n commentQueries.membersToNotify.mockRejectedValueOnce(new Error('members failed'))\n const loggerSpy = jest.spyOn(commentsManager['logger'], 'error').mockImplementation(() => undefined as any)\n\n const res = await commentsManager.createComment(user, makeSpace(), { fileId: -1, content: 'hello' } as any)\n // Let the microtask run the catch of createComment\n await new Promise((r) => setImmediate(r))\n\n expect(filesQueries.getOrCreateSpaceFile).toHaveBeenCalled()\n expect(filesQueries.getSpaceFileId).not.toHaveBeenCalled()\n expect(commentQueries.createComment).toHaveBeenCalledWith(42, 555, 'hello')\n expect(notificationsManager.create).not.toHaveBeenCalled()\n expect(res).toEqual({ id: 777, fileId: 555, content: 'hello' })\n // Verify that the catch of createComment logged the error\n expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('createComment'))\n loggerSpy.mockRestore()\n })\n\n it('notifies members when present', async () => {\n filesQueries.getSpaceFileId.mockResolvedValue(10)\n commentQueries.createComment.mockResolvedValue(1)\n commentQueries.getComments.mockResolvedValue([{ id: 1, fileId: 10, content: 'c' }])\n commentQueries.membersToNotify.mockResolvedValue([{ id: 2, email: 'a@b.c' }])\n // Force rejection of notification creation to trigger the catch in notify()\n notificationsManager.create.mockRejectedValueOnce(new Error('notify failed'))\n const loggerSpy = jest.spyOn(commentsManager['logger'], 'error').mockImplementation(() => undefined as any)\n\n await commentsManager.createComment(user, makeSpace(), { fileId: 10, content: 'c' } as any)\n // Let the microtask execute the internal catch of notify()\n await new Promise((r) => setImmediate(r))\n\n expect(notificationsManager.create).toHaveBeenCalledTimes(1)\n notificationsManager.create.mockClear()\n expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('notify'))\n loggerSpy.mockRestore()\n\n const space = makeSpace()\n await commentsManager.createComment(user, space, { fileId: 10, content: 'c' } as any)\n\n expect(commentQueries.membersToNotify).toHaveBeenCalledWith(42, 10)\n expect(notificationsManager.create).toHaveBeenCalledTimes(1)\n const [members, notification, data] = notificationsManager.create.mock.calls[0]\n expect(members).toEqual([{ id: 2, email: 'a@b.c' }])\n expect(notification).toMatchObject({\n app: expect.anything(),\n event: expect.anything(),\n element: 'file.txt',\n url: '/space/folder'\n })\n expect(fileName).toHaveBeenCalledWith(space.url)\n expect(dirName).toHaveBeenCalledWith(space.url)\n expect(data).toMatchObject({\n author: user,\n currentUrl: 'https://app.local/path',\n content: 'c'\n })\n })\n\n it('logs an error if notificationsManager.create rejects (covers catch in notify)', async () => {\n filesQueries.getSpaceFileId.mockResolvedValue(10)\n commentQueries.createComment.mockResolvedValue(1)\n commentQueries.getComments.mockResolvedValue([{ id: 1, fileId: 10, content: 'c' }])\n commentQueries.membersToNotify.mockResolvedValue([{ id: 2, email: 'a@b.c' }])\n // Force rejection to trigger the catch in notify()\n notificationsManager.create.mockRejectedValueOnce(new Error('notify failed'))\n const loggerSpy = jest.spyOn(commentsManager['logger'], 'error').mockImplementation(() => undefined as any)\n\n await commentsManager.createComment(user, makeSpace(), { fileId: 10, content: 'c' } as any)\n // Allow the microtask to run the internal catch of notify()\n await new Promise((r) => setImmediate(r))\n\n expect(notificationsManager.create).toHaveBeenCalledTimes(1)\n expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('notify'))\n loggerSpy.mockRestore()\n })\n\n it('does not notify if no members', async () => {\n filesQueries.getSpaceFileId.mockResolvedValue(10)\n commentQueries.createComment.mockResolvedValue(1)\n commentQueries.getComments.mockResolvedValue([{ id: 1 }])\n commentQueries.membersToNotify.mockResolvedValue([])\n\n await commentsManager.createComment(user, makeSpace(), { fileId: 10, content: 'c' } as any)\n\n expect(notificationsManager.create).not.toHaveBeenCalled()\n })\n })\n\n describe('updateComment', () => {\n it('rejects NOT_FOUND if target comment is not found', async () => {\n commentQueries.getComments.mockResolvedValue([])\n\n await expect(commentsManager.updateComment(user, makeSpace(), { commentId: 99, fileId: 1, content: 'z' } as any)).rejects.toMatchObject({\n status: HttpStatus.NOT_FOUND\n })\n })\n\n it('rejects BAD_REQUEST if fileId mismatches', async () => {\n commentQueries.getComments.mockResolvedValue([{ id: 50, fileId: 123 }])\n filesQueries.getSpaceFileId.mockResolvedValue(999)\n\n await expect(commentsManager.updateComment(user, makeSpace(), { commentId: 50, fileId: 123, content: 'z' } as any)).rejects.toMatchObject({\n status: HttpStatus.BAD_REQUEST\n })\n })\n\n it('rejects INTERNAL_SERVER_ERROR if update fails', async () => {\n commentQueries.getComments.mockResolvedValueOnce([{ id: 50, fileId: 5 }])\n filesQueries.getSpaceFileId.mockResolvedValue(5)\n commentQueries.updateComment.mockResolvedValue(false)\n\n await expect(commentsManager.updateComment(user, makeSpace(), { commentId: 50, fileId: 5, content: 'z' } as any)).rejects.toMatchObject({\n status: HttpStatus.INTERNAL_SERVER_ERROR\n })\n })\n\n it('returns the comment after update', async () => {\n commentQueries.getComments\n .mockResolvedValueOnce([{ id: 50, fileId: 5 }]) // initial fetch\n .mockResolvedValueOnce([{ id: 50, fileId: 5, content: 'updated' }]) // fetch after update -> include content\n filesQueries.getSpaceFileId.mockResolvedValue(5)\n commentQueries.updateComment.mockResolvedValue(true)\n\n const res = await commentsManager.updateComment(user, makeSpace(), { commentId: 50, fileId: 5, content: 'updated' } as any)\n\n expect(commentQueries.updateComment).toHaveBeenCalledWith(42, 50, 5, 'updated')\n // allow additional fields via toMatchObject\n expect(res).toMatchObject({ id: 50, fileId: 5, content: 'updated' })\n })\n })\n\n describe('deleteComment', () => {\n it('rejects BAD_REQUEST if fileId mismatches', async () => {\n filesQueries.getSpaceFileId.mockResolvedValue(10)\n\n await expect(commentsManager.deleteComment(user, makeSpace(), { commentId: 1, fileId: 11 } as any)).rejects.toMatchObject({\n status: HttpStatus.BAD_REQUEST\n })\n })\n\n it('rejects FORBIDDEN if deletion is denied', async () => {\n filesQueries.getSpaceFileId.mockResolvedValue(10)\n commentQueries.deleteComment.mockResolvedValue(false)\n\n await expect(commentsManager.deleteComment(user, makeSpace(), { commentId: 1, fileId: 10 } as any)).rejects.toMatchObject({\n status: HttpStatus.FORBIDDEN\n })\n })\n\n it('resolves when deletion succeeds', async () => {\n filesQueries.getSpaceFileId.mockResolvedValue(10)\n commentQueries.deleteComment.mockResolvedValue(true)\n\n await expect(commentsManager.deleteComment(user, makeSpace(), { commentId: 1, fileId: 10 } as any)).resolves.toBeUndefined()\n })\n })\n\n describe('getRecents', () => {\n it('delegates to commentQueries.getRecentsFromUser', async () => {\n const recents = [{ id: 1 }, { id: 2 }]\n commentQueries.getRecentsFromUser.mockResolvedValue(recents)\n\n const res = await commentsManager.getRecents(user, 5)\n\n expect(commentQueries.getRecentsFromUser).toHaveBeenCalledWith(user, 5)\n expect(res).toBe(recents)\n })\n })\n})\n"],"names":["jest","mock","isPathExists","fn","getProps","dirName","fileName","describe","CommentsManager","name","commentsManager","contextManager","commentQueries","filesQueries","notificationsManager","user","id","email","makeSpace","overrides","realPath","url","dbFile","path","ownerId","spaceExternalRootId","shareExternalId","beforeAll","getComments","createComment","updateComment","deleteComment","getRecentsFromUser","membersToNotify","getSpaceFileId","getOrCreateSpaceFile","create","mockResolvedValue","undefined","get","mockReturnValue","module","Test","createTestingModule","providers","provide","DB_TOKEN_PROVIDER","useValue","Cache","NotificationsManager","ContextManager","useClass","CommentsQueries","FilesQueries","SpacesQueries","SharesQueries","compile","beforeEach","clearAllMocks","it","expect","toBeDefined","res","toEqual","toHaveBeenCalledTimes","not","toHaveBeenCalled","expected","toHaveBeenCalledWith","toBe","rejects","toThrow","HttpException","toMatchObject","status","HttpStatus","NOT_FOUND","space","fileId","content","BAD_REQUEST","space2","mockRejectedValueOnce","Error","loggerSpy","spyOn","mockImplementation","Promise","r","setImmediate","stringContaining","mockRestore","mockClear","members","notification","data","calls","app","anything","event","element","author","currentUrl","commentId","mockResolvedValueOnce","INTERNAL_SERVER_ERROR","FORBIDDEN","resolves","toBeUndefined","recents","getRecents"],"mappings":"AAAA;;;;CAIC;;;;wBAEyC;yBACN;8BACd;uCACS;2BACG;qCACL;uBAC6B;6CACrB;sCACP;sCACA;wCACE;wCACA;AAEhC,kDAAkD;AAClDA,KAAKC,IAAI,CAAC,2BAA2B,IAAO,CAAA;QAC1CC,cAAcF,KAAKG,EAAE;QACrBC,UAAUJ,KAAKG,EAAE;QACjBE,SAASL,KAAKG,EAAE;QAChBG,UAAUN,KAAKG,EAAE;IACnB,CAAA;AAEAI,SAASC,uCAAe,CAACC,IAAI,EAAE;IAC7B,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IAQJ,IAAIC;IAIJ,IAAIC;IAEJ,MAAMC,OAAO;QAAEC,IAAI;QAAIC,OAAO;IAAe;IAE7C,MAAMC,YAAY,CAACC,YAA0B,CAAC,CAAC,GAC5C,CAAA;YACCC,UAAU;YACVC,KAAK;YACLC,QAAQ;gBACNC,MAAM;gBACNC,SAAS;gBACTC,qBAAqB;gBACrBC,iBAAiB;YACnB;YACA,GAAGP,SAAS;QACd,CAAA;IAEFQ,UAAU;QACRf,iBAAiB;YACfgB,aAAa5B,KAAKG,EAAE;YACpB0B,eAAe7B,KAAKG,EAAE;YACtB2B,eAAe9B,KAAKG,EAAE;YACtB4B,eAAe/B,KAAKG,EAAE;YACtB6B,oBAAoBhC,KAAKG,EAAE;YAC3B8B,iBAAiBjC,KAAKG,EAAE;QAC1B;QACAU,eAAe;YACbqB,gBAAgBlC,KAAKG,EAAE;YACvBgC,sBAAsBnC,KAAKG,EAAE;QAC/B;QACAW,uBAAuB;YACrBsB,QAAQpC,KAAKG,EAAE,GAAGkC,iBAAiB,CAACC;QACtC;QACA3B,iBAAiB;YACf4B,KAAKvC,KAAKG,EAAE,GAAGqC,eAAe,CAAC;QACjC;QAEA,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,WAAW;gBACT;oBACEC,SAASC,4BAAiB;oBAC1BC,UAAU,CAAC;gBACb;gBACA;oBAAEF,SAASG,mBAAK;oBAAED,UAAU,CAAC;gBAAE;gBAC/B;oBAAEF,SAASI,iDAAoB;oBAAEF,UAAUjC;gBAAqB;gBAChE;oBAAE+B,SAASK,qCAAc;oBAAEH,UAAUpC;gBAAe;gBACpD;oBAAEkC,SAASrC,uCAAe;oBAAE2C,UAAU3C,uCAAe;gBAAC;gBACtD;oBAAEqC,SAASO,uCAAe;oBAAEL,UAAUnC;gBAAe;gBACrD;oBAAEiC,SAASQ,iCAAY;oBAAEN,UAAUlC;gBAAa;gBAChD;oBAAEgC,SAASS,mCAAa;oBAAEP,UAAU,CAAC;gBAAE;gBACvC;oBAAEF,SAASU,mCAAa;oBAAER,UAAU,CAAC;gBAAE;aACxC;QACH,GAAGS,OAAO;QAEV9C,kBAAkB+B,OAAOF,GAAG,CAAkB/B,uCAAe;IAC/D;IAEAiD,WAAW;QACTzD,KAAK0D,aAAa;QAChBxD,mBAAY,CAAemC,iBAAiB,CAAC;QAC7CjC,eAAQ,CAAeiC,iBAAiB,CAAC;YAAE5B,MAAM;YAAYc,MAAM;QAAS;QAC5ElB,cAAO,CAAemC,eAAe,CAAC;QACtClC,eAAQ,CAAekC,eAAe,CAAC;IAC3C;IAEAmB,GAAG,qBAAqB;QACtBC,OAAOlD,iBAAiBmD,WAAW;IACrC;IAEAtD,SAAS,eAAe;QACtBoD,GAAG,2BAA2B;YAC5B9C,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAE9C,MAAMyB,MAAM,MAAMpD,gBAAgBkB,WAAW,CAACb,MAAMG;YAEpD0C,OAAOE,KAAKC,OAAO,CAAC,EAAE;YACtBH,OAAO/C,aAAaqB,cAAc,EAAE8B,qBAAqB,CAAC;YAC1DJ,OAAOhD,eAAegB,WAAW,EAAEqC,GAAG,CAACC,gBAAgB;QACzD;QAEAP,GAAG,uCAAuC;YACxC9C,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAC9C,MAAM8B,WAAW;gBAAC;oBAAEnD,IAAI;gBAAE;gBAAG;oBAAEA,IAAI;gBAAE;aAAE;YACvCJ,eAAegB,WAAW,CAACS,iBAAiB,CAAC8B;YAE7C,MAAML,MAAM,MAAMpD,gBAAgBkB,WAAW,CAACb,MAAMG;YAEpD0C,OAAO/C,aAAaqB,cAAc,EAAEgC,gBAAgB;YACpDN,OAAOhD,eAAegB,WAAW,EAAEwC,oBAAoB,CAAC,IAAI,MAAM;YAClER,OAAOE,KAAKO,IAAI,CAACF;QACnB;QAEAR,GAAG,2CAA2C;;YAC1CzD,mBAAY,CAAemC,iBAAiB,CAAC;YAE/C,MAAMuB,OAAOlD,gBAAgBkB,WAAW,CAACb,MAAMG,cAAcoD,OAAO,CAACC,OAAO,CAACC,qBAAa;YAC1F,MAAMZ,OAAOlD,gBAAgBkB,WAAW,CAACb,MAAMG,cAAcoD,OAAO,CAACG,aAAa,CAAC;gBAAEC,QAAQC,kBAAU,CAACC,SAAS;YAAC;QACpH;IACF;IAEArE,SAAS,iBAAiB;QACxBoD,GAAG,8CAA8C;YAC/C,MAAMkB,QAAQ3D,UAAU;gBACtBI,QAAQ;oBAAEC,MAAM;oBAAKC,SAAS;oBAAIC,qBAAqB;oBAAOC,iBAAiB;gBAAK;YACtF;YACA,MAAMkC,OAAOlD,gBAAgBmB,aAAa,CAACd,MAAM8D,OAAO;gBAAEC,QAAQ;gBAAGC,SAAS;YAAK,IAAWT,OAAO,CAACG,aAAa,CAAC;gBAClHC,QAAQC,kBAAU,CAACK,WAAW;YAChC;YAEA,MAAMC,SAAS/D,UAAU;gBACvBI,QAAQ;oBAAEC,MAAM;oBAAKC,SAAS;oBAAIC,qBAAqB;oBAAMC,iBAAiB;gBAAM;YACtF;YACA,MAAMkC,OAAOlD,gBAAgBmB,aAAa,CAACd,MAAMkE,QAAQ;gBAAEH,QAAQ;gBAAGC,SAAS;YAAK,IAAWT,OAAO,CAACG,aAAa,CAAC;gBACnHC,QAAQC,kBAAU,CAACK,WAAW;YAChC;QACF;QAEArB,GAAG,qDAAqD;YACtD9C,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAE9C,MAAMuB,OAAOlD,gBAAgBmB,aAAa,CAACd,MAAMG,aAAa;gBAAE4D,QAAQ;gBAAKC,SAAS;YAAI,IAAWT,OAAO,CAACG,aAAa,CAAC;gBACzHC,QAAQC,kBAAU,CAACK,WAAW;YAChC;QACF;QAEArB,GAAG,oCAAoC;YACrC9C,aAAasB,oBAAoB,CAACE,iBAAiB,CAAC;YACpDzB,eAAeiB,aAAa,CAACQ,iBAAiB,CAAC;YAC/CzB,eAAegB,WAAW,CAACS,iBAAiB,CAAC;gBAAC;oBAAErB,IAAI;oBAAK8D,QAAQ;oBAAKC,SAAS;gBAAQ;aAAE;YACzF,iGAAiG;YACjGnE,eAAeqB,eAAe,CAACiD,qBAAqB,CAAC,IAAIC,MAAM;YAC/D,MAAMC,YAAYpF,KAAKqF,KAAK,CAAC3E,eAAe,CAAC,SAAS,EAAE,SAAS4E,kBAAkB,CAAC,IAAMhD;YAE1F,MAAMwB,MAAM,MAAMpD,gBAAgBmB,aAAa,CAACd,MAAMG,aAAa;gBAAE4D,QAAQ,CAAC;gBAAGC,SAAS;YAAQ;YAClG,mDAAmD;YACnD,MAAM,IAAIQ,QAAQ,CAACC,IAAMC,aAAaD;YAEtC5B,OAAO/C,aAAasB,oBAAoB,EAAE+B,gBAAgB;YAC1DN,OAAO/C,aAAaqB,cAAc,EAAE+B,GAAG,CAACC,gBAAgB;YACxDN,OAAOhD,eAAeiB,aAAa,EAAEuC,oBAAoB,CAAC,IAAI,KAAK;YACnER,OAAO9C,qBAAqBsB,MAAM,EAAE6B,GAAG,CAACC,gBAAgB;YACxDN,OAAOE,KAAKC,OAAO,CAAC;gBAAE/C,IAAI;gBAAK8D,QAAQ;gBAAKC,SAAS;YAAQ;YAC7D,0DAA0D;YAC1DnB,OAAOwB,WAAWhB,oBAAoB,CAACR,OAAO8B,gBAAgB,CAAC;YAC/DN,UAAUO,WAAW;QACvB;QAEAhC,GAAG,iCAAiC;YAClC9C,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAC9CzB,eAAeiB,aAAa,CAACQ,iBAAiB,CAAC;YAC/CzB,eAAegB,WAAW,CAACS,iBAAiB,CAAC;gBAAC;oBAAErB,IAAI;oBAAG8D,QAAQ;oBAAIC,SAAS;gBAAI;aAAE;YAClFnE,eAAeqB,eAAe,CAACI,iBAAiB,CAAC;gBAAC;oBAAErB,IAAI;oBAAGC,OAAO;gBAAQ;aAAE;YAC5E,4EAA4E;YAC5EH,qBAAqBsB,MAAM,CAAC8C,qBAAqB,CAAC,IAAIC,MAAM;YAC5D,MAAMC,YAAYpF,KAAKqF,KAAK,CAAC3E,eAAe,CAAC,SAAS,EAAE,SAAS4E,kBAAkB,CAAC,IAAMhD;YAE1F,MAAM5B,gBAAgBmB,aAAa,CAACd,MAAMG,aAAa;gBAAE4D,QAAQ;gBAAIC,SAAS;YAAI;YAClF,2DAA2D;YAC3D,MAAM,IAAIQ,QAAQ,CAACC,IAAMC,aAAaD;YAEtC5B,OAAO9C,qBAAqBsB,MAAM,EAAE4B,qBAAqB,CAAC;YAC1DlD,qBAAqBsB,MAAM,CAACwD,SAAS;YACrChC,OAAOwB,WAAWhB,oBAAoB,CAACR,OAAO8B,gBAAgB,CAAC;YAC/DN,UAAUO,WAAW;YAErB,MAAMd,QAAQ3D;YACd,MAAMR,gBAAgBmB,aAAa,CAACd,MAAM8D,OAAO;gBAAEC,QAAQ;gBAAIC,SAAS;YAAI;YAE5EnB,OAAOhD,eAAeqB,eAAe,EAAEmC,oBAAoB,CAAC,IAAI;YAChER,OAAO9C,qBAAqBsB,MAAM,EAAE4B,qBAAqB,CAAC;YAC1D,MAAM,CAAC6B,SAASC,cAAcC,KAAK,GAAGjF,qBAAqBsB,MAAM,CAACnC,IAAI,CAAC+F,KAAK,CAAC,EAAE;YAC/EpC,OAAOiC,SAAS9B,OAAO,CAAC;gBAAC;oBAAE/C,IAAI;oBAAGC,OAAO;gBAAQ;aAAE;YACnD2C,OAAOkC,cAAcrB,aAAa,CAAC;gBACjCwB,KAAKrC,OAAOsC,QAAQ;gBACpBC,OAAOvC,OAAOsC,QAAQ;gBACtBE,SAAS;gBACT/E,KAAK;YACP;YACAuC,OAAOtD,eAAQ,EAAE8D,oBAAoB,CAACS,MAAMxD,GAAG;YAC/CuC,OAAOvD,cAAO,EAAE+D,oBAAoB,CAACS,MAAMxD,GAAG;YAC9CuC,OAAOmC,MAAMtB,aAAa,CAAC;gBACzB4B,QAAQtF;gBACRuF,YAAY;gBACZvB,SAAS;YACX;QACF;QAEApB,GAAG,iFAAiF;YAClF9C,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAC9CzB,eAAeiB,aAAa,CAACQ,iBAAiB,CAAC;YAC/CzB,eAAegB,WAAW,CAACS,iBAAiB,CAAC;gBAAC;oBAAErB,IAAI;oBAAG8D,QAAQ;oBAAIC,SAAS;gBAAI;aAAE;YAClFnE,eAAeqB,eAAe,CAACI,iBAAiB,CAAC;gBAAC;oBAAErB,IAAI;oBAAGC,OAAO;gBAAQ;aAAE;YAC5E,mDAAmD;YACnDH,qBAAqBsB,MAAM,CAAC8C,qBAAqB,CAAC,IAAIC,MAAM;YAC5D,MAAMC,YAAYpF,KAAKqF,KAAK,CAAC3E,eAAe,CAAC,SAAS,EAAE,SAAS4E,kBAAkB,CAAC,IAAMhD;YAE1F,MAAM5B,gBAAgBmB,aAAa,CAACd,MAAMG,aAAa;gBAAE4D,QAAQ;gBAAIC,SAAS;YAAI;YAClF,4DAA4D;YAC5D,MAAM,IAAIQ,QAAQ,CAACC,IAAMC,aAAaD;YAEtC5B,OAAO9C,qBAAqBsB,MAAM,EAAE4B,qBAAqB,CAAC;YAC1DJ,OAAOwB,WAAWhB,oBAAoB,CAACR,OAAO8B,gBAAgB,CAAC;YAC/DN,UAAUO,WAAW;QACvB;QAEAhC,GAAG,iCAAiC;YAClC9C,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAC9CzB,eAAeiB,aAAa,CAACQ,iBAAiB,CAAC;YAC/CzB,eAAegB,WAAW,CAACS,iBAAiB,CAAC;gBAAC;oBAAErB,IAAI;gBAAE;aAAE;YACxDJ,eAAeqB,eAAe,CAACI,iBAAiB,CAAC,EAAE;YAEnD,MAAM3B,gBAAgBmB,aAAa,CAACd,MAAMG,aAAa;gBAAE4D,QAAQ;gBAAIC,SAAS;YAAI;YAElFnB,OAAO9C,qBAAqBsB,MAAM,EAAE6B,GAAG,CAACC,gBAAgB;QAC1D;IACF;IAEA3D,SAAS,iBAAiB;QACxBoD,GAAG,oDAAoD;YACrD/C,eAAegB,WAAW,CAACS,iBAAiB,CAAC,EAAE;YAE/C,MAAMuB,OAAOlD,gBAAgBoB,aAAa,CAACf,MAAMG,aAAa;gBAAEqF,WAAW;gBAAIzB,QAAQ;gBAAGC,SAAS;YAAI,IAAWT,OAAO,CAACG,aAAa,CAAC;gBACtIC,QAAQC,kBAAU,CAACC,SAAS;YAC9B;QACF;QAEAjB,GAAG,4CAA4C;YAC7C/C,eAAegB,WAAW,CAACS,iBAAiB,CAAC;gBAAC;oBAAErB,IAAI;oBAAI8D,QAAQ;gBAAI;aAAE;YACtEjE,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAE9C,MAAMuB,OAAOlD,gBAAgBoB,aAAa,CAACf,MAAMG,aAAa;gBAAEqF,WAAW;gBAAIzB,QAAQ;gBAAKC,SAAS;YAAI,IAAWT,OAAO,CAACG,aAAa,CAAC;gBACxIC,QAAQC,kBAAU,CAACK,WAAW;YAChC;QACF;QAEArB,GAAG,iDAAiD;YAClD/C,eAAegB,WAAW,CAAC4E,qBAAqB,CAAC;gBAAC;oBAAExF,IAAI;oBAAI8D,QAAQ;gBAAE;aAAE;YACxEjE,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAC9CzB,eAAekB,aAAa,CAACO,iBAAiB,CAAC;YAE/C,MAAMuB,OAAOlD,gBAAgBoB,aAAa,CAACf,MAAMG,aAAa;gBAAEqF,WAAW;gBAAIzB,QAAQ;gBAAGC,SAAS;YAAI,IAAWT,OAAO,CAACG,aAAa,CAAC;gBACtIC,QAAQC,kBAAU,CAAC8B,qBAAqB;YAC1C;QACF;QAEA9C,GAAG,oCAAoC;YACrC/C,eAAegB,WAAW,CACvB4E,qBAAqB,CAAC;gBAAC;oBAAExF,IAAI;oBAAI8D,QAAQ;gBAAE;aAAE,EAAE,gBAAgB;aAC/D0B,qBAAqB,CAAC;gBAAC;oBAAExF,IAAI;oBAAI8D,QAAQ;oBAAGC,SAAS;gBAAU;aAAE,GAAE,wCAAwC;YAC9GlE,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAC9CzB,eAAekB,aAAa,CAACO,iBAAiB,CAAC;YAE/C,MAAMyB,MAAM,MAAMpD,gBAAgBoB,aAAa,CAACf,MAAMG,aAAa;gBAAEqF,WAAW;gBAAIzB,QAAQ;gBAAGC,SAAS;YAAU;YAElHnB,OAAOhD,eAAekB,aAAa,EAAEsC,oBAAoB,CAAC,IAAI,IAAI,GAAG;YACrE,4CAA4C;YAC5CR,OAAOE,KAAKW,aAAa,CAAC;gBAAEzD,IAAI;gBAAI8D,QAAQ;gBAAGC,SAAS;YAAU;QACpE;IACF;IAEAxE,SAAS,iBAAiB;QACxBoD,GAAG,4CAA4C;YAC7C9C,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAE9C,MAAMuB,OAAOlD,gBAAgBqB,aAAa,CAAChB,MAAMG,aAAa;gBAAEqF,WAAW;gBAAGzB,QAAQ;YAAG,IAAWR,OAAO,CAACG,aAAa,CAAC;gBACxHC,QAAQC,kBAAU,CAACK,WAAW;YAChC;QACF;QAEArB,GAAG,2CAA2C;YAC5C9C,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAC9CzB,eAAemB,aAAa,CAACM,iBAAiB,CAAC;YAE/C,MAAMuB,OAAOlD,gBAAgBqB,aAAa,CAAChB,MAAMG,aAAa;gBAAEqF,WAAW;gBAAGzB,QAAQ;YAAG,IAAWR,OAAO,CAACG,aAAa,CAAC;gBACxHC,QAAQC,kBAAU,CAAC+B,SAAS;YAC9B;QACF;QAEA/C,GAAG,mCAAmC;YACpC9C,aAAaqB,cAAc,CAACG,iBAAiB,CAAC;YAC9CzB,eAAemB,aAAa,CAACM,iBAAiB,CAAC;YAE/C,MAAMuB,OAAOlD,gBAAgBqB,aAAa,CAAChB,MAAMG,aAAa;gBAAEqF,WAAW;gBAAGzB,QAAQ;YAAG,IAAW6B,QAAQ,CAACC,aAAa;QAC5H;IACF;IAEArG,SAAS,cAAc;QACrBoD,GAAG,kDAAkD;YACnD,MAAMkD,UAAU;gBAAC;oBAAE7F,IAAI;gBAAE;gBAAG;oBAAEA,IAAI;gBAAE;aAAE;YACtCJ,eAAeoB,kBAAkB,CAACK,iBAAiB,CAACwE;YAEpD,MAAM/C,MAAM,MAAMpD,gBAAgBoG,UAAU,CAAC/F,MAAM;YAEnD6C,OAAOhD,eAAeoB,kBAAkB,EAAEoC,oBAAoB,CAACrD,MAAM;YACrE6C,OAAOE,KAAKO,IAAI,CAACwC;QACnB;IACF;AACF"}