@sync-in/server 1.3.9 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +5 -3
  3. package/environment/environment.dist.yaml +2 -0
  4. package/package.json +6 -6
  5. package/server/app.bootstrap.js +9 -0
  6. package/server/app.bootstrap.js.map +1 -1
  7. package/server/app.service.spec.js +44 -19
  8. package/server/app.service.spec.js.map +1 -1
  9. package/server/applications/comments/comments.controller.spec.js +103 -4
  10. package/server/applications/comments/comments.controller.spec.js.map +1 -1
  11. package/server/applications/comments/services/comments-manager.service.spec.js +409 -9
  12. package/server/applications/comments/services/comments-manager.service.spec.js.map +1 -1
  13. package/server/applications/files/adapters/files-indexer-mysql.service.spec.js +333 -0
  14. package/server/applications/files/adapters/files-indexer-mysql.service.spec.js.map +1 -0
  15. package/server/applications/files/constants/files.js +0 -23
  16. package/server/applications/files/constants/files.js.map +1 -1
  17. package/server/applications/files/constants/only-office.js +8 -0
  18. package/server/applications/files/constants/only-office.js.map +1 -1
  19. package/server/applications/files/constants/routes.js +6 -1
  20. package/server/applications/files/constants/routes.js.map +1 -1
  21. package/server/applications/files/files-only-office.controller.js +11 -0
  22. package/server/applications/files/files-only-office.controller.js.map +1 -1
  23. package/server/applications/files/files-only-office.controller.spec.js +97 -3
  24. package/server/applications/files/files-only-office.controller.spec.js.map +1 -1
  25. package/server/applications/files/files-tasks.controller.spec.js +91 -1
  26. package/server/applications/files/files-tasks.controller.spec.js.map +1 -1
  27. package/server/applications/files/files.config.js +5 -0
  28. package/server/applications/files/files.config.js.map +1 -1
  29. package/server/applications/files/files.controller.spec.js +268 -46
  30. package/server/applications/files/files.controller.spec.js.map +1 -1
  31. package/server/applications/files/guards/files-only-office.guard.spec.js +77 -1
  32. package/server/applications/files/guards/files-only-office.guard.spec.js.map +1 -1
  33. package/server/applications/files/guards/files-only-office.strategy.js +0 -1
  34. package/server/applications/files/guards/files-only-office.strategy.js.map +1 -1
  35. package/server/applications/files/services/files-only-office-manager.service.js +5 -0
  36. package/server/applications/files/services/files-only-office-manager.service.js.map +1 -1
  37. package/server/applications/links/links.controller.spec.js +91 -58
  38. package/server/applications/links/links.controller.spec.js.map +1 -1
  39. package/server/applications/links/services/links-manager.service.js +4 -6
  40. package/server/applications/links/services/links-manager.service.js.map +1 -1
  41. package/server/applications/links/services/links-manager.service.spec.js +378 -14
  42. package/server/applications/links/services/links-manager.service.spec.js.map +1 -1
  43. package/server/applications/links/services/links-queries.service.js +1 -1
  44. package/server/applications/links/services/links-queries.service.js.map +1 -1
  45. package/server/applications/notifications/notifications.controller.spec.js +56 -1
  46. package/server/applications/notifications/notifications.controller.spec.js.map +1 -1
  47. package/server/applications/notifications/services/notifications-manager.service.spec.js +461 -5
  48. package/server/applications/notifications/services/notifications-manager.service.spec.js.map +1 -1
  49. package/server/applications/shares/services/shares-manager.service.spec.js +590 -14
  50. package/server/applications/shares/services/shares-manager.service.spec.js.map +1 -1
  51. package/server/applications/spaces/guards/space.guard.spec.js +153 -18
  52. package/server/applications/spaces/guards/space.guard.spec.js.map +1 -1
  53. package/server/applications/spaces/services/spaces-browser.service.js +7 -7
  54. package/server/applications/spaces/services/spaces-browser.service.js.map +1 -1
  55. package/server/applications/spaces/services/spaces-manager.service.js +17 -17
  56. package/server/applications/spaces/services/spaces-manager.service.js.map +1 -1
  57. package/server/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.js +120 -0
  58. package/server/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.js.map +1 -0
  59. package/server/applications/sync/services/sync-clients-manager.service.spec.js +548 -8
  60. package/server/applications/sync/services/sync-clients-manager.service.spec.js.map +1 -1
  61. package/server/applications/sync/services/sync-manager.service.spec.js +837 -5
  62. package/server/applications/sync/services/sync-manager.service.spec.js.map +1 -1
  63. package/server/applications/sync/services/sync-paths-manager.service.spec.js +900 -7
  64. package/server/applications/sync/services/sync-paths-manager.service.spec.js.map +1 -1
  65. package/server/applications/sync/utils/routes.js +1 -1
  66. package/server/applications/sync/utils/routes.js.map +1 -1
  67. package/server/applications/users/guards/permissions.guard.js +4 -4
  68. package/server/applications/users/guards/permissions.guard.js.map +1 -1
  69. package/server/applications/users/guards/permissions.guard.spec.js +6 -6
  70. package/server/applications/users/guards/permissions.guard.spec.js.map +1 -1
  71. package/server/applications/users/guards/roles.guard.js +1 -1
  72. package/server/applications/users/guards/roles.guard.js.map +1 -1
  73. package/server/applications/users/models/user.model.js +1 -1
  74. package/server/applications/users/models/user.model.js.map +1 -1
  75. package/server/applications/users/services/admin-users-manager.service.js +22 -24
  76. package/server/applications/users/services/admin-users-manager.service.js.map +1 -1
  77. package/server/applications/users/services/admin-users-manager.service.spec.js +763 -17
  78. package/server/applications/users/services/admin-users-manager.service.spec.js.map +1 -1
  79. package/server/applications/users/services/users-manager.service.js +1 -1
  80. package/server/applications/users/services/users-manager.service.js.map +1 -1
  81. package/server/applications/users/services/users-manager.service.spec.js +938 -49
  82. package/server/applications/users/services/users-manager.service.spec.js.map +1 -1
  83. package/server/applications/webdav/decorators/if-header.decorator.js +4 -1
  84. package/server/applications/webdav/decorators/if-header.decorator.js.map +1 -1
  85. package/server/applications/webdav/filters/webdav.filter.spec.js +77 -0
  86. package/server/applications/webdav/filters/webdav.filter.spec.js.map +1 -0
  87. package/server/applications/webdav/guards/webdav-protocol.guard.js +3 -7
  88. package/server/applications/webdav/guards/webdav-protocol.guard.js.map +1 -1
  89. package/server/applications/webdav/guards/webdav-protocol.guard.spec.js +580 -0
  90. package/server/applications/webdav/guards/webdav-protocol.guard.spec.js.map +1 -0
  91. package/server/applications/webdav/services/webdav-methods.service.spec.js +1582 -3
  92. package/server/applications/webdav/services/webdav-methods.service.spec.js.map +1 -1
  93. package/server/applications/webdav/services/webdav-spaces.service.spec.js +390 -2
  94. package/server/applications/webdav/services/webdav-spaces.service.spec.js.map +1 -1
  95. package/server/applications/webdav/webdav.controller.js +2 -2
  96. package/server/applications/webdav/webdav.controller.js.map +1 -1
  97. package/server/authentication/guards/auth-basic.guard.js.map +1 -1
  98. package/server/authentication/guards/auth-basic.guard.spec.js +38 -2
  99. package/server/authentication/guards/auth-basic.guard.spec.js.map +1 -1
  100. package/server/authentication/guards/auth-basic.strategy.js +0 -1
  101. package/server/authentication/guards/auth-basic.strategy.js.map +1 -1
  102. package/server/authentication/guards/auth-digest.guard.js +1 -2
  103. package/server/authentication/guards/auth-digest.guard.js.map +1 -1
  104. package/server/authentication/guards/auth-local.guard.js.map +1 -1
  105. package/server/authentication/guards/auth-local.guard.spec.js +7 -5
  106. package/server/authentication/guards/auth-local.guard.spec.js.map +1 -1
  107. package/server/authentication/guards/auth-local.strategy.js +0 -1
  108. package/server/authentication/guards/auth-local.strategy.js.map +1 -1
  109. package/server/authentication/guards/auth-token-access.guard.spec.js +30 -0
  110. package/server/authentication/guards/auth-token-access.guard.spec.js.map +1 -1
  111. package/server/authentication/guards/auth-token-access.strategy.js +0 -1
  112. package/server/authentication/guards/auth-token-access.strategy.js.map +1 -1
  113. package/server/authentication/guards/auth-token-refresh.strategy.js +0 -1
  114. package/server/authentication/guards/auth-token-refresh.strategy.js.map +1 -1
  115. package/server/authentication/services/auth-methods/auth-method-database.service.js +1 -1
  116. package/server/authentication/services/auth-methods/auth-method-database.service.js.map +1 -1
  117. package/server/authentication/services/auth-methods/auth-method-database.service.spec.js +8 -6
  118. package/server/authentication/services/auth-methods/auth-method-database.service.spec.js.map +1 -1
  119. package/server/authentication/services/auth-methods/auth-method-ldap.service.js +2 -2
  120. package/server/authentication/services/auth-methods/auth-method-ldap.service.js.map +1 -1
  121. package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js +500 -5
  122. package/server/authentication/services/auth-methods/auth-method-ldap.service.spec.js.map +1 -1
  123. package/server/configuration/config.loader.js +0 -3
  124. package/server/configuration/config.loader.js.map +1 -1
  125. package/server/infrastructure/context/interceptors/context.interceptor.spec.js +135 -0
  126. package/server/infrastructure/context/interceptors/context.interceptor.spec.js.map +1 -0
  127. package/server/infrastructure/context/services/context-manager.service.spec.js +98 -0
  128. package/server/infrastructure/context/services/context-manager.service.spec.js.map +1 -0
  129. package/server/infrastructure/database/constants.js +0 -1
  130. package/server/infrastructure/database/constants.js.map +1 -1
  131. package/server/infrastructure/database/scripts/seed/usersgroups.js +3 -3
  132. package/server/infrastructure/database/scripts/seed/usersgroups.js.map +1 -1
  133. package/server/infrastructure/mailer/mailer.service.js +20 -19
  134. package/server/infrastructure/mailer/mailer.service.js.map +1 -1
  135. package/server/infrastructure/mailer/mailer.service.spec.js +176 -0
  136. package/server/infrastructure/mailer/mailer.service.spec.js.map +1 -0
  137. package/static/3rdpartylicenses.txt +26 -26
  138. package/static/assets/pdfjs/build/pdf.mjs +1177 -255
  139. package/static/assets/pdfjs/build/pdf.mjs.map +1 -1
  140. package/static/assets/pdfjs/build/pdf.sandbox.mjs +25 -2
  141. package/static/assets/pdfjs/build/pdf.sandbox.mjs.map +1 -1
  142. package/static/assets/pdfjs/build/pdf.worker.mjs +140 -16
  143. package/static/assets/pdfjs/build/pdf.worker.mjs.map +1 -1
  144. package/static/assets/pdfjs/version +1 -1
  145. package/static/assets/pdfjs/web/debugger.css +31 -0
  146. package/static/assets/pdfjs/web/debugger.mjs +144 -2
  147. package/static/assets/pdfjs/web/images/comment-editButton.svg +6 -1
  148. package/static/assets/pdfjs/web/locale/ach/viewer.ftl +0 -63
  149. package/static/assets/pdfjs/web/locale/af/viewer.ftl +0 -71
  150. package/static/assets/pdfjs/web/locale/an/viewer.ftl +0 -63
  151. package/static/assets/pdfjs/web/locale/ast/viewer.ftl +0 -60
  152. package/static/assets/pdfjs/web/locale/az/viewer.ftl +0 -63
  153. package/static/assets/pdfjs/web/locale/be/viewer.ftl +38 -0
  154. package/static/assets/pdfjs/web/locale/bg/viewer.ftl +0 -37
  155. package/static/assets/pdfjs/web/locale/bn/viewer.ftl +0 -63
  156. package/static/assets/pdfjs/web/locale/bo/viewer.ftl +0 -63
  157. package/static/assets/pdfjs/web/locale/br/viewer.ftl +0 -37
  158. package/static/assets/pdfjs/web/locale/brx/viewer.ftl +0 -63
  159. package/static/assets/pdfjs/web/locale/bs/viewer.ftl +22 -0
  160. package/static/assets/pdfjs/web/locale/ca/viewer.ftl +0 -54
  161. package/static/assets/pdfjs/web/locale/cak/viewer.ftl +0 -54
  162. package/static/assets/pdfjs/web/locale/ckb/viewer.ftl +0 -63
  163. package/static/assets/pdfjs/web/locale/cs/viewer.ftl +38 -0
  164. package/static/assets/pdfjs/web/locale/cy/viewer.ftl +38 -0
  165. package/static/assets/pdfjs/web/locale/da/viewer.ftl +38 -0
  166. package/static/assets/pdfjs/web/locale/de/viewer.ftl +38 -0
  167. package/static/assets/pdfjs/web/locale/dsb/viewer.ftl +38 -0
  168. package/static/assets/pdfjs/web/locale/el/viewer.ftl +38 -0
  169. package/static/assets/pdfjs/web/locale/en-CA/viewer.ftl +38 -0
  170. package/static/assets/pdfjs/web/locale/en-GB/viewer.ftl +38 -0
  171. package/static/assets/pdfjs/web/locale/en-US/viewer.ftl +25 -0
  172. package/static/assets/pdfjs/web/locale/eo/viewer.ftl +38 -0
  173. package/static/assets/pdfjs/web/locale/es-AR/viewer.ftl +38 -0
  174. package/static/assets/pdfjs/web/locale/es-CL/viewer.ftl +38 -0
  175. package/static/assets/pdfjs/web/locale/es-MX/viewer.ftl +0 -6
  176. package/static/assets/pdfjs/web/locale/et/viewer.ftl +0 -57
  177. package/static/assets/pdfjs/web/locale/fa/viewer.ftl +0 -37
  178. package/static/assets/pdfjs/web/locale/ff/viewer.ftl +0 -63
  179. package/static/assets/pdfjs/web/locale/fi/viewer.ftl +38 -0
  180. package/static/assets/pdfjs/web/locale/fr/viewer.ftl +38 -0
  181. package/static/assets/pdfjs/web/locale/fy-NL/viewer.ftl +38 -0
  182. package/static/assets/pdfjs/web/locale/ga-IE/viewer.ftl +0 -71
  183. package/static/assets/pdfjs/web/locale/gd/viewer.ftl +0 -54
  184. package/static/assets/pdfjs/web/locale/gl/viewer.ftl +8 -0
  185. package/static/assets/pdfjs/web/locale/gn/viewer.ftl +38 -0
  186. package/static/assets/pdfjs/web/locale/gu-IN/viewer.ftl +0 -63
  187. package/static/assets/pdfjs/web/locale/he/viewer.ftl +38 -0
  188. package/static/assets/pdfjs/web/locale/hi-IN/viewer.ftl +0 -60
  189. package/static/assets/pdfjs/web/locale/hsb/viewer.ftl +38 -0
  190. package/static/assets/pdfjs/web/locale/hu/viewer.ftl +38 -0
  191. package/static/assets/pdfjs/web/locale/hy-AM/viewer.ftl +0 -49
  192. package/static/assets/pdfjs/web/locale/hye/viewer.ftl +0 -60
  193. package/static/assets/pdfjs/web/locale/ia/viewer.ftl +38 -0
  194. package/static/assets/pdfjs/web/locale/is/viewer.ftl +0 -3
  195. package/static/assets/pdfjs/web/locale/it/viewer.ftl +31 -0
  196. package/static/assets/pdfjs/web/locale/ja/viewer.ftl +8 -0
  197. package/static/assets/pdfjs/web/locale/ka/viewer.ftl +48 -10
  198. package/static/assets/pdfjs/web/locale/kab/viewer.ftl +5 -0
  199. package/static/assets/pdfjs/web/locale/kk/viewer.ftl +8 -0
  200. package/static/assets/pdfjs/web/locale/km/viewer.ftl +0 -63
  201. package/static/assets/pdfjs/web/locale/kn/viewer.ftl +0 -71
  202. package/static/assets/pdfjs/web/locale/ko/viewer.ftl +38 -0
  203. package/static/assets/pdfjs/web/locale/lij/viewer.ftl +0 -63
  204. package/static/assets/pdfjs/web/locale/lo/viewer.ftl +0 -54
  205. package/static/assets/pdfjs/web/locale/lt/viewer.ftl +0 -60
  206. package/static/assets/pdfjs/web/locale/ltg/viewer.ftl +0 -63
  207. package/static/assets/pdfjs/web/locale/lv/viewer.ftl +0 -63
  208. package/static/assets/pdfjs/web/locale/meh/viewer.ftl +0 -75
  209. package/static/assets/pdfjs/web/locale/mk/viewer.ftl +0 -63
  210. package/static/assets/pdfjs/web/locale/ml/viewer.ftl +0 -3
  211. package/static/assets/pdfjs/web/locale/mr/viewer.ftl +0 -63
  212. package/static/assets/pdfjs/web/locale/ms/viewer.ftl +0 -63
  213. package/static/assets/pdfjs/web/locale/my/viewer.ftl +0 -71
  214. package/static/assets/pdfjs/web/locale/nb-NO/viewer.ftl +44 -6
  215. package/static/assets/pdfjs/web/locale/ne-NP/viewer.ftl +0 -71
  216. package/static/assets/pdfjs/web/locale/nl/viewer.ftl +38 -0
  217. package/static/assets/pdfjs/web/locale/nn-NO/viewer.ftl +45 -1
  218. package/static/assets/pdfjs/web/locale/oc/viewer.ftl +0 -31
  219. package/static/assets/pdfjs/web/locale/pa-IN/viewer.ftl +38 -0
  220. package/static/assets/pdfjs/web/locale/pl/viewer.ftl +39 -1
  221. package/static/assets/pdfjs/web/locale/pt-BR/viewer.ftl +38 -0
  222. package/static/assets/pdfjs/web/locale/ro/viewer.ftl +355 -1
  223. package/static/assets/pdfjs/web/locale/ru/viewer.ftl +38 -0
  224. package/static/assets/pdfjs/web/locale/sat/viewer.ftl +0 -54
  225. package/static/assets/pdfjs/web/locale/sc/viewer.ftl +0 -38
  226. package/static/assets/pdfjs/web/locale/scn/viewer.ftl +0 -92
  227. package/static/assets/pdfjs/web/locale/sco/viewer.ftl +0 -60
  228. package/static/assets/pdfjs/web/locale/si/viewer.ftl +0 -51
  229. package/static/assets/pdfjs/web/locale/sk/viewer.ftl +38 -0
  230. package/static/assets/pdfjs/web/locale/skr/viewer.ftl +0 -27
  231. package/static/assets/pdfjs/web/locale/sl/viewer.ftl +8 -0
  232. package/static/assets/pdfjs/web/locale/son/viewer.ftl +0 -71
  233. package/static/assets/pdfjs/web/locale/sr/viewer.ftl +0 -33
  234. package/static/assets/pdfjs/web/locale/sv-SE/viewer.ftl +38 -0
  235. package/static/assets/pdfjs/web/locale/szl/viewer.ftl +0 -63
  236. package/static/assets/pdfjs/web/locale/ta/viewer.ftl +0 -63
  237. package/static/assets/pdfjs/web/locale/te/viewer.ftl +0 -60
  238. package/static/assets/pdfjs/web/locale/tg/viewer.ftl +38 -0
  239. package/static/assets/pdfjs/web/locale/tl/viewer.ftl +0 -63
  240. package/static/assets/pdfjs/web/locale/tr/viewer.ftl +40 -2
  241. package/static/assets/pdfjs/web/locale/trs/viewer.ftl +0 -72
  242. package/static/assets/pdfjs/web/locale/ur/viewer.ftl +0 -60
  243. package/static/assets/pdfjs/web/locale/uz/viewer.ftl +0 -71
  244. package/static/assets/pdfjs/web/locale/vi/viewer.ftl +38 -0
  245. package/static/assets/pdfjs/web/locale/wo/viewer.ftl +0 -77
  246. package/static/assets/pdfjs/web/locale/xh/viewer.ftl +0 -71
  247. package/static/assets/pdfjs/web/locale/zh-CN/viewer.ftl +38 -0
  248. package/static/assets/pdfjs/web/locale/zh-TW/viewer.ftl +38 -0
  249. package/static/assets/pdfjs/web/viewer.css +649 -120
  250. package/static/assets/pdfjs/web/viewer.html +19 -0
  251. package/static/assets/pdfjs/web/viewer.mjs +489 -38
  252. package/static/assets/pdfjs/web/viewer.mjs.map +1 -1
  253. package/static/chunk-22EANI6R.js +1 -0
  254. package/static/{chunk-KFM544CA.js → chunk-2UWN7IQF.js} +1 -1
  255. package/static/{chunk-N3T57OCA.js → chunk-2VSPDSJS.js} +1 -1
  256. package/static/{chunk-HUWQHCUX.js → chunk-34UZ7SYI.js} +1 -1
  257. package/static/{chunk-MWFRZBJD.js → chunk-45UQJGGY.js} +1 -1
  258. package/static/{chunk-LYTD6AJE.js → chunk-5TEXH3LJ.js} +1 -1
  259. package/static/{chunk-4KESSWTF.js → chunk-66FMKVJX.js} +1 -1
  260. package/static/{chunk-XE5YHU5J.js → chunk-BIUNUYZ5.js} +1 -1
  261. package/static/chunk-CK4BY2NX.js +27 -0
  262. package/static/{chunk-QTW62OKJ.js → chunk-CSBDAY77.js} +1 -1
  263. package/static/{chunk-XUZSYWRF.js → chunk-CXXPLBDZ.js} +1 -1
  264. package/static/{chunk-ZTXJC5IC.js → chunk-EILQG525.js} +1 -1
  265. package/static/{chunk-FJFNDK67.js → chunk-ENWABUR4.js} +1 -1
  266. package/static/{chunk-WL65GYD5.js → chunk-FR4AOLYL.js} +4 -4
  267. package/static/chunk-HW2H3ISM.js +559 -0
  268. package/static/{chunk-BW5PQAKK.js → chunk-HYMDGBZL.js} +1 -1
  269. package/static/{chunk-WLPYIJFI.js → chunk-IML5UYQG.js} +1 -1
  270. package/static/{chunk-Z5X7LVMZ.js → chunk-IPSMJHMQ.js} +1 -1
  271. package/static/{chunk-3S4WNZ2T.js → chunk-JVCWYSNP.js} +1 -1
  272. package/static/{chunk-CLSVDV7J.js → chunk-KGPCIUD2.js} +1 -1
  273. package/static/{chunk-O4AQBQBF.js → chunk-KQZJSEM3.js} +1 -1
  274. package/static/{chunk-MK7WZG3F.js → chunk-NPEMJJIU.js} +1 -1
  275. package/static/{chunk-4TEHM3AS.js → chunk-OEFBC4GG.js} +1 -1
  276. package/static/{chunk-O67RFAWU.js → chunk-P734A3XZ.js} +1 -1
  277. package/static/{chunk-SRLMFJ7C.js → chunk-RASR4CK6.js} +1 -1
  278. package/static/{chunk-S5WXHO6D.js → chunk-RFMOUC22.js} +1 -1
  279. package/static/{chunk-TTQ37MUV.js → chunk-RSS6GYNE.js} +1 -1
  280. package/static/{chunk-3FX6ISDY.js → chunk-SBOQGGZX.js} +1 -1
  281. package/static/{chunk-NV2MEIWP.js → chunk-SJAFPXQV.js} +1 -1
  282. package/static/{chunk-PYSFXLMV.js → chunk-XTYGMF2V.js} +1 -1
  283. package/static/{chunk-ZFKCGL6X.js → chunk-YCWMV2YR.js} +1 -1
  284. package/static/{chunk-LB7B5RIV.js → chunk-YGD22MWQ.js} +1 -1
  285. package/static/{chunk-MTRNPGS4.js → chunk-ZC5NIT55.js} +1 -1
  286. package/static/{chunk-SKDQM65G.js → chunk-ZVY37DKS.js} +1 -1
  287. package/static/index.html +2 -2
  288. package/static/main-N5CZRHAO.js +7 -0
  289. package/static/styles-FYUSO6OJ.css +1 -0
  290. package/static/chunk-AY2GOSJ2.js +0 -24
  291. package/static/chunk-RSNLYAN6.js +0 -560
  292. package/static/chunk-ZZ3LHYOY.js +0 -1
  293. package/static/main-RREKR34B.js +0 -10
  294. package/static/styles-3DONJ2Z4.css +0 -1
@@ -6,36 +6,1615 @@
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 _fileslockmanagerservice = require("../../files/services/files-lock-manager.service");
11
12
  const _filesmanagerservice = require("../../files/services/files-manager.service");
13
+ const _files = require("../../files/utils/files");
14
+ const _spaces = require("../../spaces/constants/spaces");
15
+ const _paths = /*#__PURE__*/ _interop_require_wildcard(require("../../spaces/utils/paths"));
16
+ const _permissions = require("../../spaces/utils/permissions");
17
+ const _webdav = require("../constants/webdav");
18
+ const _ifheader = /*#__PURE__*/ _interop_require_wildcard(require("../utils/if-header"));
12
19
  const _webdavmethodsservice = require("./webdav-methods.service");
13
20
  const _webdavspacesservice = require("./webdav-spaces.service");
21
+ function _getRequireWildcardCache(nodeInterop) {
22
+ if (typeof WeakMap !== "function") return null;
23
+ var cacheBabelInterop = new WeakMap();
24
+ var cacheNodeInterop = new WeakMap();
25
+ return (_getRequireWildcardCache = function(nodeInterop) {
26
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
27
+ })(nodeInterop);
28
+ }
29
+ function _interop_require_wildcard(obj, nodeInterop) {
30
+ if (!nodeInterop && obj && obj.__esModule) {
31
+ return obj;
32
+ }
33
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
34
+ return {
35
+ default: obj
36
+ };
37
+ }
38
+ var cache = _getRequireWildcardCache(nodeInterop);
39
+ if (cache && cache.has(obj)) {
40
+ return cache.get(obj);
41
+ }
42
+ var newObj = {
43
+ __proto__: null
44
+ };
45
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
46
+ for(var key in obj){
47
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
48
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
49
+ if (desc && (desc.get || desc.set)) {
50
+ Object.defineProperty(newObj, key, desc);
51
+ } else {
52
+ newObj[key] = obj[key];
53
+ }
54
+ }
55
+ }
56
+ newObj.default = obj;
57
+ if (cache) {
58
+ cache.set(obj, newObj);
59
+ }
60
+ return newObj;
61
+ }
62
+ jest.mock('../../files/utils/files', ()=>({
63
+ isPathExists: jest.fn(),
64
+ dirName: jest.fn(),
65
+ genEtag: jest.fn().mockReturnValue('W/"etag"')
66
+ }));
67
+ jest.mock('../../spaces/utils/permissions', ()=>({
68
+ haveSpaceEnvPermissions: jest.fn()
69
+ }));
70
+ jest.mock('../../spaces/utils/paths', ()=>{
71
+ const actual = jest.requireActual('../../spaces/utils/paths');
72
+ return {
73
+ ...actual,
74
+ dbFileFromSpace: jest.fn()
75
+ };
76
+ });
77
+ jest.mock('../decorators/if-header.decorator', ()=>({
78
+ IfHeaderDecorator: ()=>(_target, _key, _desc)=>undefined
79
+ }));
14
80
  describe(_webdavmethodsservice.WebDAVMethods.name, ()=>{
15
81
  let service;
82
+ let filesManager;
83
+ let filesLockManager;
84
+ let webDAVSpaces;
85
+ const makeRes = ()=>{
86
+ const res = {
87
+ statusCode: undefined,
88
+ body: undefined,
89
+ headers: {},
90
+ contentType: undefined,
91
+ status (code) {
92
+ this.statusCode = code;
93
+ return this;
94
+ },
95
+ send (payload) {
96
+ this.body = payload;
97
+ return this;
98
+ },
99
+ header (name, value) {
100
+ this.headers[name.toLowerCase()] = value;
101
+ return this;
102
+ },
103
+ type (ct) {
104
+ this.contentType = ct;
105
+ return this;
106
+ }
107
+ };
108
+ return res;
109
+ };
110
+ const baseReq = (overrides = {})=>({
111
+ method: 'GET',
112
+ user: {
113
+ id: 1,
114
+ login: 'user-1'
115
+ },
116
+ dav: {
117
+ url: '/webdav/url',
118
+ depth: '0',
119
+ httpVersion: 'HTTP/1.1',
120
+ body: '<lockrequest/>',
121
+ lock: {
122
+ timeout: 60,
123
+ lockscope: 'exclusive',
124
+ owner: 'user-1',
125
+ token: 'opaquetoken:abc'
126
+ }
127
+ },
128
+ space: {
129
+ id: 10,
130
+ alias: 'spaceA',
131
+ url: '/spaces/spaceA/file.txt',
132
+ realPath: '/real/path/file.txt',
133
+ inSharesList: false,
134
+ dbFile: {
135
+ path: 'file.txt'
136
+ }
137
+ },
138
+ ...overrides
139
+ });
16
140
  beforeAll(async ()=>{
141
+ filesManager = {
142
+ sendFileFromSpace: jest.fn(),
143
+ mkFile: jest.fn().mockResolvedValue(undefined),
144
+ saveStream: jest.fn(),
145
+ delete: jest.fn().mockResolvedValue(undefined),
146
+ touch: jest.fn().mockResolvedValue(undefined),
147
+ mkDir: jest.fn().mockResolvedValue(undefined),
148
+ copyMove: jest.fn().mockResolvedValue(undefined)
149
+ };
150
+ filesLockManager = {
151
+ create: jest.fn(),
152
+ isLockedWithToken: jest.fn(),
153
+ removeLock: jest.fn().mockResolvedValue(undefined),
154
+ browseLocks: jest.fn(),
155
+ browseParentChildLocks: jest.fn(),
156
+ checkConflicts: jest.fn().mockResolvedValue(undefined),
157
+ getLocksByPath: jest.fn(),
158
+ getLockByToken: jest.fn(),
159
+ refreshLockTimeout: jest.fn().mockResolvedValue(undefined)
160
+ };
161
+ webDAVSpaces = {
162
+ propfind: jest.fn(),
163
+ spaceEnv: jest.fn()
164
+ };
17
165
  const module = await _testing.Test.createTestingModule({
18
166
  providers: [
19
167
  _webdavmethodsservice.WebDAVMethods,
20
168
  {
21
169
  provide: _webdavspacesservice.WebDAVSpaces,
22
- useValue: {}
170
+ useValue: webDAVSpaces
23
171
  },
24
172
  {
25
173
  provide: _filesmanagerservice.FilesManager,
26
- useValue: {}
174
+ useValue: filesManager
27
175
  },
28
176
  {
29
177
  provide: _fileslockmanagerservice.FilesLockManager,
30
- useValue: {}
178
+ useValue: filesLockManager
31
179
  }
32
180
  ]
33
181
  }).compile();
182
+ module.useLogger([
183
+ 'fatal'
184
+ ]);
34
185
  service = module.get(_webdavmethodsservice.WebDAVMethods);
35
186
  });
187
+ beforeEach(()=>{
188
+ jest.clearAllMocks();
189
+ _files.isPathExists.mockReset().mockResolvedValue(true);
190
+ _files.dirName.mockReturnValue('/real/path');
191
+ _permissions.haveSpaceEnvPermissions.mockReturnValue(true);
192
+ });
193
+ afterEach(()=>{
194
+ jest.restoreAllMocks();
195
+ });
36
196
  it('should be defined', ()=>{
37
197
  expect(service).toBeDefined();
38
198
  });
199
+ describe('headOrGet', ()=>{
200
+ it('streams the file when repository is FILES and not in shares list', async ()=>{
201
+ const req = baseReq();
202
+ const res = makeRes();
203
+ const streamable = {
204
+ stream: 'ok'
205
+ };
206
+ const send = {
207
+ checks: jest.fn().mockResolvedValue(undefined),
208
+ stream: jest.fn().mockResolvedValue(streamable)
209
+ };
210
+ filesManager.sendFileFromSpace.mockReturnValue(send);
211
+ const result = await service.headOrGet(req, res, _spaces.SPACE_REPOSITORY.FILES);
212
+ expect(filesManager.sendFileFromSpace).toHaveBeenCalledWith(req.space);
213
+ expect(send.checks).toHaveBeenCalledTimes(1);
214
+ expect(send.stream).toHaveBeenCalledWith(req, res);
215
+ expect(result).toBe(streamable);
216
+ });
217
+ it('returns 403 when repository is not allowed', async ()=>{
218
+ const req = baseReq({
219
+ space: {
220
+ ...baseReq().space,
221
+ inSharesList: true
222
+ }
223
+ });
224
+ const res = makeRes();
225
+ // repository not FILES or inSharesList true => forbidden
226
+ await service.headOrGet(req, res, 'OTHER');
227
+ expect(res.statusCode).toBe(_common.HttpStatus.FORBIDDEN);
228
+ expect(res.body).toBe('Not allowed on this resource');
229
+ });
230
+ it('handles error thrown by sendFile.checks via handleError', async ()=>{
231
+ const req = baseReq();
232
+ const res = makeRes();
233
+ const send = {
234
+ checks: jest.fn().mockRejectedValue(new Error('boom')),
235
+ stream: jest.fn()
236
+ };
237
+ filesManager.sendFileFromSpace.mockReturnValue(send);
238
+ const handleSpy = jest.spyOn(service, 'handleError').mockReturnValue('handled');
239
+ const result = await service.headOrGet(req, res, _spaces.SPACE_REPOSITORY.FILES);
240
+ expect(handleSpy).toHaveBeenCalled();
241
+ expect(result).toBe('handled');
242
+ });
243
+ });
244
+ describe('lock', ()=>{
245
+ it('when body is empty: returns 400 if resource does not exist (lock refresh)', async ()=>{
246
+ ;
247
+ _files.isPathExists.mockResolvedValue(false);
248
+ const req = baseReq({
249
+ dav: {
250
+ ...baseReq().dav,
251
+ body: undefined
252
+ }
253
+ });
254
+ const res = makeRes();
255
+ await service.lock(req, res);
256
+ expect(res.statusCode).toBe(_common.HttpStatus.BAD_REQUEST);
257
+ expect(res.body).toBe('Lock refresh must specify an existing resource');
258
+ });
259
+ it('when body is empty: delegates to lockRefresh if resource exists', async ()=>{
260
+ ;
261
+ _files.isPathExists.mockResolvedValue(true);
262
+ const req = baseReq({
263
+ dav: {
264
+ ...baseReq().dav,
265
+ body: undefined
266
+ }
267
+ });
268
+ const res = makeRes();
269
+ const lockRefreshSpy = jest.spyOn(service, 'lockRefresh').mockResolvedValue('ok');
270
+ const result = await service.lock(req, res);
271
+ expect(lockRefreshSpy).toHaveBeenCalledWith(req, res, req.space.dbFile.path);
272
+ expect(result).toBe('ok');
273
+ });
274
+ it('returns 403 when creating new lock on non-existing resource without permission', async ()=>{
275
+ ;
276
+ _files.isPathExists.mockResolvedValueOnce(false) // resource does not exist
277
+ ;
278
+ _permissions.haveSpaceEnvPermissions.mockReturnValue(false);
279
+ const req = baseReq();
280
+ const res = makeRes();
281
+ await service.lock(req, res);
282
+ expect(res.statusCode).toBe(_common.HttpStatus.FORBIDDEN);
283
+ expect(res.body).toBe('You are not allowed to do this action');
284
+ expect(filesLockManager.create).not.toHaveBeenCalled();
285
+ });
286
+ it('returns 409 when parent does not exist for new lock', async ()=>{
287
+ ;
288
+ _files.isPathExists.mockResolvedValueOnce(false) // resource does not exist
289
+ .mockResolvedValueOnce(false) // parent does not exist
290
+ ;
291
+ _permissions.haveSpaceEnvPermissions.mockReturnValue(true);
292
+ _files.dirName.mockReturnValue('/real/path/parent-missing');
293
+ const req = baseReq();
294
+ const res = makeRes();
295
+ await service.lock(req, res);
296
+ expect(res.statusCode).toBe(_common.HttpStatus.CONFLICT);
297
+ expect(res.body).toBe('Parent must exists');
298
+ expect(filesLockManager.create).not.toHaveBeenCalled();
299
+ });
300
+ it('creates lock on existing resource and returns 200 with lock-token header', async ()=>{
301
+ ;
302
+ _files.isPathExists.mockResolvedValue(true); // resource exists
303
+ const req = baseReq();
304
+ const res = makeRes();
305
+ filesLockManager.create.mockImplementation(async (_user, _dbFile, _depth, _timeout, davLock)=>{
306
+ davLock.locktoken = 'opaquetoken:1';
307
+ return [
308
+ true,
309
+ {
310
+ dbFilePath: _dbFile?.path,
311
+ davLock: {
312
+ lockroot: davLock.lockroot,
313
+ locktoken: davLock.locktoken,
314
+ lockscope: davLock.lockscope,
315
+ owner: davLock.owner,
316
+ depth: _depth,
317
+ timeout: _timeout
318
+ }
319
+ }
320
+ ];
321
+ });
322
+ await service.lock(req, res);
323
+ expect(filesLockManager.create).toHaveBeenCalledTimes(1);
324
+ expect(res.headers['lock-token']).toBe('<opaquetoken:1>');
325
+ expect(res.statusCode).toBe(_common.HttpStatus.OK);
326
+ expect(res.contentType).toBe('application/xml; charset=utf-8');
327
+ expect(res.body).toBeDefined();
328
+ });
329
+ it('creates lock on unmapped URL (resource missing), creates empty file and returns 201', async ()=>{
330
+ ;
331
+ _files.isPathExists.mockResolvedValueOnce(false) // resource missing
332
+ .mockResolvedValueOnce(true); // parent exists
333
+ const req = baseReq();
334
+ const res = makeRes();
335
+ filesLockManager.create.mockImplementation(async (_user, _dbFile, _depth, _timeout, davLock)=>{
336
+ davLock.locktoken = 'opaquetoken:new';
337
+ return [
338
+ true,
339
+ {
340
+ dbFilePath: _dbFile?.path,
341
+ davLock: {
342
+ lockroot: davLock.lockroot,
343
+ locktoken: davLock.locktoken,
344
+ lockscope: davLock.lockscope,
345
+ owner: davLock.owner,
346
+ depth: _depth,
347
+ timeout: _timeout
348
+ }
349
+ }
350
+ ];
351
+ });
352
+ await service.lock(req, res);
353
+ expect(filesManager.mkFile).toHaveBeenCalledTimes(1);
354
+ expect(res.statusCode).toBe(_common.HttpStatus.CREATED);
355
+ expect(res.headers['lock-token']).toBe('<opaquetoken:new>');
356
+ });
357
+ it('returns 423 when a lock conflict occurs', async ()=>{
358
+ ;
359
+ _files.isPathExists.mockResolvedValue(true);
360
+ const req = baseReq();
361
+ const res = makeRes();
362
+ filesLockManager.create.mockResolvedValue([
363
+ false,
364
+ {
365
+ davLock: {
366
+ lockroot: '/locked'
367
+ },
368
+ dbFilePath: 'file.txt'
369
+ }
370
+ ]);
371
+ await service.lock(req, res);
372
+ // DAV_ERROR_RES should have set 423 on the response
373
+ expect(res.statusCode).toBe(_common.HttpStatus.LOCKED);
374
+ });
375
+ });
376
+ describe('unlock', ()=>{
377
+ it('returns 404 when resource does not exist', async ()=>{
378
+ ;
379
+ _files.isPathExists.mockResolvedValue(false);
380
+ const req = baseReq();
381
+ const res = makeRes();
382
+ await service.unlock(req, res);
383
+ expect(res.statusCode).toBe(_common.HttpStatus.NOT_FOUND);
384
+ expect(res.body).toBe(req.dav.url);
385
+ });
386
+ it('returns 409 when lock token does not exist or does not match URL', async ()=>{
387
+ ;
388
+ _files.isPathExists.mockResolvedValue(true);
389
+ filesLockManager.isLockedWithToken.mockResolvedValue(null);
390
+ const req = baseReq();
391
+ const res = makeRes();
392
+ await service.unlock(req, res);
393
+ expect(filesLockManager.isLockedWithToken).toHaveBeenCalledWith(req.dav.lock.token, req.space.dbFile.path);
394
+ expect(res.statusCode).toBe(_common.HttpStatus.CONFLICT);
395
+ });
396
+ it('returns 403 when the lock owner is a different user', async ()=>{
397
+ ;
398
+ _files.isPathExists.mockResolvedValue(true);
399
+ filesLockManager.isLockedWithToken.mockResolvedValue({
400
+ owner: {
401
+ id: 2
402
+ },
403
+ key: 'k1'
404
+ });
405
+ const req = baseReq();
406
+ const res = makeRes();
407
+ await service.unlock(req, res);
408
+ expect(res.statusCode).toBe(_common.HttpStatus.FORBIDDEN);
409
+ expect(res.body).toBe('Token was created by another user');
410
+ expect(filesLockManager.removeLock).not.toHaveBeenCalled();
411
+ });
412
+ it('removes lock and returns 204 when owner matches', async ()=>{
413
+ ;
414
+ _files.isPathExists.mockResolvedValue(true);
415
+ filesLockManager.isLockedWithToken.mockResolvedValue({
416
+ owner: {
417
+ id: 1
418
+ },
419
+ key: 'k2'
420
+ });
421
+ const req = baseReq();
422
+ const res = makeRes();
423
+ await service.unlock(req, res);
424
+ expect(filesLockManager.removeLock).toHaveBeenCalledWith('k2');
425
+ expect(res.statusCode).toBe(_common.HttpStatus.NO_CONTENT);
426
+ });
427
+ });
428
+ describe('propfind', ()=>{
429
+ it('returns 404 when repository is FILES and path does not exist', async ()=>{
430
+ ;
431
+ _files.isPathExists.mockResolvedValue(false);
432
+ const req = baseReq({
433
+ dav: {
434
+ ...baseReq().dav,
435
+ propfindMode: 'prop'
436
+ }
437
+ });
438
+ const res = makeRes();
439
+ const result = await service.propfind(req, res, _spaces.SPACE_REPOSITORY.FILES);
440
+ expect(result).toBe(res);
441
+ expect(res.statusCode).toBe(_common.HttpStatus.NOT_FOUND);
442
+ expect(res.body).toBe(req.dav.url);
443
+ });
444
+ it('returns multistatus with only property names when PROPNAME mode', async ()=>{
445
+ ;
446
+ _files.isPathExists.mockResolvedValue(true);
447
+ const req = baseReq({
448
+ dav: {
449
+ ...baseReq().dav,
450
+ propfindMode: 'propname',
451
+ httpVersion: 'HTTP/1.1'
452
+ }
453
+ });
454
+ const res = makeRes();
455
+ const handler = service['webDAVHandler'];
456
+ jest.spyOn(handler, 'propfind').mockImplementation(async function*() {
457
+ yield {
458
+ href: '/a',
459
+ name: 'file.txt'
460
+ };
461
+ });
462
+ const result = await service.propfind(req, res, _spaces.SPACE_REPOSITORY.FILES);
463
+ expect(result).toBe(res);
464
+ expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
465
+ expect(res.contentType).toContain('application/xml');
466
+ expect(typeof res.body).toBe('string');
467
+ expect(res.body).toContain('/a');
468
+ });
469
+ it('collects lock discovery based on depth', async ()=>{
470
+ ;
471
+ _files.isPathExists.mockResolvedValue(true);
472
+ const req0 = baseReq({
473
+ dav: {
474
+ ...baseReq().dav,
475
+ body: {
476
+ propfind: {
477
+ prop: {
478
+ [_webdav.STANDARD_PROPS[0]]: ''
479
+ }
480
+ }
481
+ },
482
+ propfindMode: 'prop',
483
+ httpVersion: 'HTTP/1.1',
484
+ depth: '0'
485
+ }
486
+ });
487
+ const reqInf = baseReq({
488
+ dav: {
489
+ ...baseReq().dav,
490
+ body: {
491
+ propfind: {
492
+ prop: {
493
+ [_webdav.STANDARD_PROPS[0]]: ''
494
+ }
495
+ }
496
+ },
497
+ propfindMode: 'prop',
498
+ httpVersion: 'HTTP/1.1',
499
+ depth: 'infinity'
500
+ }
501
+ });
502
+ const res0 = makeRes();
503
+ const resInf = makeRes();
504
+ const handler = service['webDAVHandler'];
505
+ jest.spyOn(handler, 'propfind').mockImplementation(async function*() {
506
+ yield {
507
+ href: '/a',
508
+ name: 'file.txt',
509
+ getlastmodified: 'x'
510
+ };
511
+ });
512
+ filesLockManager.browseLocks.mockResolvedValue({
513
+ 'file.txt': {
514
+ davLock: {
515
+ lockroot: '/dav/url'
516
+ }
517
+ }
518
+ });
519
+ await service.propfind(req0, res0, _spaces.SPACE_REPOSITORY.FILES);
520
+ expect(filesLockManager.browseLocks).toHaveBeenCalledTimes(1);
521
+ filesLockManager.browseParentChildLocks.mockResolvedValue({
522
+ 'file.txt': {
523
+ davLock: {
524
+ lockroot: '/dav/url'
525
+ }
526
+ }
527
+ });
528
+ await service.propfind(reqInf, resInf, _spaces.SPACE_REPOSITORY.FILES);
529
+ expect(filesLockManager.browseParentChildLocks).toHaveBeenCalledTimes(1);
530
+ });
531
+ it('includes lockdiscovery when requested', async ()=>{
532
+ ;
533
+ _files.isPathExists.mockResolvedValue(true);
534
+ const req = baseReq({
535
+ dav: {
536
+ ...baseReq().dav,
537
+ propfindMode: 'prop',
538
+ httpVersion: 'HTTP/1.1',
539
+ body: {
540
+ propfind: {
541
+ prop: {
542
+ lockdiscovery: ''
543
+ }
544
+ }
545
+ }
546
+ }
547
+ });
548
+ const res = makeRes();
549
+ const handler = service['webDAVHandler'];
550
+ jest.spyOn(handler, 'propfind').mockImplementation(async function*() {
551
+ yield {
552
+ href: '/a',
553
+ name: 'file.txt'
554
+ };
555
+ });
556
+ filesLockManager.browseLocks.mockResolvedValue({
557
+ 'file.txt': {
558
+ davLock: {
559
+ lockroot: '/dav/url'
560
+ }
561
+ }
562
+ });
563
+ await service.propfind(req, res, _spaces.SPACE_REPOSITORY.FILES);
564
+ expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
565
+ expect(typeof res.body).toBe('string');
566
+ expect(res.body).toContain('/dav/url');
567
+ });
568
+ });
569
+ describe('put', ()=>{
570
+ it.each([
571
+ {
572
+ existed: true,
573
+ expected: _common.HttpStatus.NO_CONTENT,
574
+ checkEtag: true
575
+ },
576
+ {
577
+ existed: false,
578
+ expected: _common.HttpStatus.CREATED,
579
+ checkEtag: false
580
+ }
581
+ ])('returns correct status for PUT when existed=%s', async ({ existed, expected, checkEtag })=>{
582
+ filesManager.saveStream.mockResolvedValue(existed);
583
+ const req = baseReq({
584
+ method: 'PUT'
585
+ });
586
+ const res = makeRes();
587
+ const result = await service.put(req, res);
588
+ if (checkEtag) {
589
+ expect(res.headers['etag']).toBeDefined();
590
+ expect(result).toBe(res);
591
+ }
592
+ expect(res.statusCode).toBe(expected);
593
+ });
594
+ it('delegates errors to handleError', async ()=>{
595
+ const req = baseReq({
596
+ method: 'PUT'
597
+ });
598
+ const res = makeRes();
599
+ const err = new Error('save failed');
600
+ filesManager.saveStream.mockRejectedValue(err);
601
+ const spy = jest.spyOn(service, 'handleError').mockReturnValue('handled');
602
+ const result = await service.put(req, res);
603
+ expect(spy).toHaveBeenCalled();
604
+ expect(result).toBe('handled');
605
+ });
606
+ });
607
+ describe('delete', ()=>{
608
+ it('returns 204 on success', async ()=>{
609
+ const req = baseReq({
610
+ method: 'DELETE'
611
+ });
612
+ const res = makeRes();
613
+ const result = await service.delete(req, res);
614
+ expect(result).toBe(res);
615
+ expect(res.statusCode).toBe(_common.HttpStatus.NO_CONTENT);
616
+ });
617
+ it('delegates errors to handleError', async ()=>{
618
+ const req = baseReq({
619
+ method: 'DELETE'
620
+ });
621
+ const res = makeRes();
622
+ const err = new Error('delete failed');
623
+ filesManager.delete.mockRejectedValue(err);
624
+ const spy = jest.spyOn(service, 'handleError').mockReturnValue('handled');
625
+ const result = await service.delete(req, res);
626
+ expect(spy).toHaveBeenCalled();
627
+ expect(result).toBe('handled');
628
+ });
629
+ });
630
+ describe('proppatch', ()=>{
631
+ it('returns 404 when target does not exist', async ()=>{
632
+ ;
633
+ _files.isPathExists.mockResolvedValue(false);
634
+ const req = baseReq({
635
+ method: 'PROPPATCH',
636
+ dav: {
637
+ ...baseReq().dav,
638
+ url: '/x',
639
+ body: {
640
+ propertyupdate: {}
641
+ }
642
+ }
643
+ });
644
+ const res = makeRes();
645
+ await service.proppatch(req, res);
646
+ expect(res.statusCode).toBe(_common.HttpStatus.NOT_FOUND);
647
+ expect(res.body).toBe('/x');
648
+ });
649
+ it('returns 400 for unknown action tag', async ()=>{
650
+ ;
651
+ _files.isPathExists.mockResolvedValue(true);
652
+ const req = baseReq({
653
+ method: 'PROPPATCH',
654
+ dav: {
655
+ ...baseReq().dav,
656
+ body: {
657
+ propertyupdate: {
658
+ unknown: {}
659
+ }
660
+ }
661
+ }
662
+ });
663
+ const res = makeRes();
664
+ await service.proppatch(req, res);
665
+ expect(res.statusCode).toBe(_common.HttpStatus.BAD_REQUEST);
666
+ expect(res.body).toContain('Unknown tag');
667
+ });
668
+ it('returns 400 when missing prop tag', async ()=>{
669
+ ;
670
+ _files.isPathExists.mockResolvedValue(true);
671
+ const req = baseReq({
672
+ method: 'PROPPATCH',
673
+ dav: {
674
+ ...baseReq().dav,
675
+ body: {
676
+ propertyupdate: {
677
+ set: {
678
+ foo: 'bar'
679
+ }
680
+ }
681
+ }
682
+ }
683
+ });
684
+ const res = makeRes();
685
+ await service.proppatch(req, res);
686
+ expect(res.statusCode).toBe(_common.HttpStatus.BAD_REQUEST);
687
+ expect(res.body).toContain('Unknown tag');
688
+ });
689
+ it('returns 207 with errors when unsupported props are provided', async ()=>{
690
+ ;
691
+ _files.isPathExists.mockResolvedValue(true);
692
+ filesLockManager.checkConflicts.mockResolvedValue(undefined);
693
+ const req = baseReq({
694
+ method: 'PROPPATCH',
695
+ dav: {
696
+ ...baseReq().dav,
697
+ httpVersion: 'HTTP/1.1',
698
+ body: {
699
+ propertyupdate: {
700
+ set: {
701
+ prop: [
702
+ {
703
+ randomProp: 'x'
704
+ }
705
+ ]
706
+ }
707
+ }
708
+ }
709
+ }
710
+ });
711
+ const res = makeRes();
712
+ await service.proppatch(req, res);
713
+ expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
714
+ expect(res.contentType).toContain('application/xml');
715
+ expect(typeof res.body).toBe('string');
716
+ expect(res.body).toContain('randomProp');
717
+ expect(res.body).toContain('403');
718
+ });
719
+ it('applies modified props and still returns 207; failed dependency if one fails', async ()=>{
720
+ ;
721
+ _files.isPathExists.mockResolvedValue(true);
722
+ filesLockManager.checkConflicts.mockResolvedValue(undefined);
723
+ filesManager.touch.mockResolvedValueOnce(undefined);
724
+ const req = baseReq({
725
+ method: 'PROPPATCH',
726
+ dav: {
727
+ ...baseReq().dav,
728
+ httpVersion: 'HTTP/1.1',
729
+ body: {
730
+ propertyupdate: {
731
+ set: {
732
+ prop: [
733
+ {
734
+ lastmodified: '2024-01-01'
735
+ },
736
+ {
737
+ ['Win32CreationTime']: 'keep'
738
+ }
739
+ ]
740
+ }
741
+ }
742
+ }
743
+ }
744
+ });
745
+ const res = makeRes();
746
+ await service.proppatch(req, res);
747
+ expect(filesManager.touch).toHaveBeenCalled();
748
+ expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
749
+ expect(typeof res.body).toBe('string');
750
+ expect(res.body).toContain('lastmodified');
751
+ expect(res.body).toContain('200');
752
+ });
753
+ it('delegates lock conflict to handleError when checkConflicts throws', async ()=>{
754
+ ;
755
+ _files.isPathExists.mockResolvedValue(true);
756
+ const req = baseReq({
757
+ method: 'PROPPATCH',
758
+ dav: {
759
+ ...baseReq().dav,
760
+ body: {
761
+ propertyupdate: {
762
+ set: {
763
+ prop: [
764
+ {
765
+ lastmodified: 'x'
766
+ }
767
+ ]
768
+ }
769
+ }
770
+ }
771
+ }
772
+ });
773
+ const res = makeRes();
774
+ const err = new Error('conflict');
775
+ filesLockManager.checkConflicts.mockRejectedValue(err);
776
+ const spy = jest.spyOn(service, 'handleError').mockReturnValue('handled');
777
+ const result = await service.proppatch(req, res);
778
+ expect(spy).toHaveBeenCalled();
779
+ expect(result).toBe('handled');
780
+ });
781
+ it('normalizes array of propertyupdate items containing {prop: ...}', async ()=>{
782
+ ;
783
+ _files.isPathExists.mockResolvedValue(true);
784
+ filesLockManager.checkConflicts.mockResolvedValue(undefined);
785
+ filesManager.touch.mockResolvedValueOnce(undefined);
786
+ const req = baseReq({
787
+ method: 'PROPPATCH',
788
+ dav: {
789
+ ...baseReq().dav,
790
+ httpVersion: 'HTTP/1.1',
791
+ body: {
792
+ propertyupdate: {
793
+ set: [
794
+ {
795
+ prop: {
796
+ lastmodified: '2024-03-01'
797
+ }
798
+ },
799
+ {
800
+ prop: {
801
+ ['Win32CreationTime']: 'ignore'
802
+ }
803
+ }
804
+ ]
805
+ }
806
+ }
807
+ }
808
+ });
809
+ const res = makeRes();
810
+ await service.proppatch(req, res);
811
+ expect(filesManager.touch).toHaveBeenCalled();
812
+ expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
813
+ expect(res.contentType).toContain('application/xml');
814
+ expect(typeof res.body).toBe('string');
815
+ expect(res.body).toContain('lastmodified');
816
+ expect(res.body).toContain('200');
817
+ });
818
+ it('wraps single prop object into an array for processing', async ()=>{
819
+ ;
820
+ _files.isPathExists.mockResolvedValue(true);
821
+ filesLockManager.checkConflicts.mockResolvedValue(undefined);
822
+ const req = baseReq({
823
+ method: 'PROPPATCH',
824
+ dav: {
825
+ ...baseReq().dav,
826
+ httpVersion: 'HTTP/1.1',
827
+ body: {
828
+ propertyupdate: {
829
+ set: {
830
+ prop: {
831
+ lastmodified: '2024-03-02'
832
+ }
833
+ }
834
+ }
835
+ }
836
+ }
837
+ });
838
+ const res = makeRes();
839
+ await service.proppatch(req, res);
840
+ expect(filesManager.touch).toHaveBeenCalled();
841
+ expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
842
+ expect(typeof res.body).toBe('string');
843
+ expect(res.body).toContain('lastmodified');
844
+ expect(res.body).toContain('200');
845
+ });
846
+ it('handles REMOVE action on supported property and returns 207', async ()=>{
847
+ ;
848
+ _files.isPathExists.mockResolvedValue(true);
849
+ filesLockManager.checkConflicts.mockResolvedValue(undefined);
850
+ const req = baseReq({
851
+ method: 'PROPPATCH',
852
+ dav: {
853
+ ...baseReq().dav,
854
+ httpVersion: 'HTTP/1.1',
855
+ body: {
856
+ propertyupdate: {
857
+ remove: {
858
+ prop: [
859
+ {
860
+ ['Win32CreationTime']: ''
861
+ }
862
+ ]
863
+ }
864
+ }
865
+ }
866
+ }
867
+ });
868
+ const res = makeRes();
869
+ await service.proppatch(req, res);
870
+ expect(filesManager.touch).not.toHaveBeenCalled();
871
+ expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
872
+ expect(res.contentType).toContain('application/xml');
873
+ expect(typeof res.body).toBe('string');
874
+ expect(res.body).toContain('Win32CreationTime');
875
+ expect(res.body).toContain('200');
876
+ });
877
+ it('returns 207 with 424 Failed Dependency when touching lastmodified fails', async ()=>{
878
+ ;
879
+ _files.isPathExists.mockResolvedValue(true);
880
+ filesLockManager.checkConflicts.mockResolvedValue(undefined);
881
+ filesManager.touch.mockRejectedValueOnce(new Error('touch failed'));
882
+ const req = baseReq({
883
+ method: 'PROPPATCH',
884
+ dav: {
885
+ ...baseReq().dav,
886
+ httpVersion: 'HTTP/1.1',
887
+ body: {
888
+ propertyupdate: {
889
+ set: {
890
+ prop: [
891
+ {
892
+ lastmodified: '2024-01-01'
893
+ },
894
+ {
895
+ ['Win32CreationTime']: 'ok'
896
+ }
897
+ ]
898
+ }
899
+ }
900
+ }
901
+ }
902
+ });
903
+ const res = makeRes();
904
+ await service.proppatch(req, res);
905
+ expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
906
+ expect(typeof res.body).toBe('string');
907
+ expect(res.body).toContain('424');
908
+ expect(res.body).toContain('lastmodified');
909
+ });
910
+ it('returns 207 with 403 for unsupported prop and 424 for supported prop as failed dependency', async ()=>{
911
+ // Préparation : la ressource existe et pas de conflit de lock
912
+ ;
913
+ _files.isPathExists.mockResolvedValue(true);
914
+ filesLockManager.checkConflicts.mockResolvedValue(undefined);
915
+ // On envoie à la fois une prop non supportée ('randomProp') et une prop supportée ('Win32CreationTime')
916
+ const req = baseReq({
917
+ method: 'PROPPATCH',
918
+ dav: {
919
+ ...baseReq().dav,
920
+ httpVersion: 'HTTP/1.1',
921
+ body: {
922
+ propertyupdate: {
923
+ set: {
924
+ prop: [
925
+ {
926
+ randomProp: 'x'
927
+ },
928
+ {
929
+ Win32CreationTime: 'keep'
930
+ }
931
+ ]
932
+ }
933
+ }
934
+ }
935
+ }
936
+ });
937
+ const res = makeRes();
938
+ // Exécution
939
+ await service.proppatch(req, res);
940
+ // Assertions : multistatus, xml, et contenu
941
+ expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
942
+ expect(res.contentType).toContain('application/xml');
943
+ const xml = res.body;
944
+ // On doit trouver le nom de la prop non supportée avec status 403
945
+ expect(xml).toContain('randomProp');
946
+ expect(xml).toContain('403');
947
+ // On doit trouver le nom de la prop supportée avec status 424 (failed dependency)
948
+ expect(xml).toContain('Win32CreationTime');
949
+ expect(xml).toContain('424');
950
+ });
951
+ });
952
+ describe('mkcol', ()=>{
953
+ it('returns 201 when directory created', async ()=>{
954
+ const req = baseReq({
955
+ method: 'MKCOL'
956
+ });
957
+ const res = makeRes();
958
+ await service.mkcol(req, res);
959
+ expect(filesManager.mkDir).toHaveBeenCalled();
960
+ expect(res.statusCode).toBe(_common.HttpStatus.CREATED);
961
+ });
962
+ it('delegates errors to handleError', async ()=>{
963
+ const req = baseReq({
964
+ method: 'MKCOL'
965
+ });
966
+ const res = makeRes();
967
+ filesManager.mkDir.mockRejectedValue(new Error('mkdir failed'));
968
+ const spy = jest.spyOn(service, 'handleError').mockReturnValue('handled');
969
+ const result = await service.mkcol(req, res);
970
+ expect(spy).toHaveBeenCalled();
971
+ expect(result).toBe('handled');
972
+ });
973
+ });
974
+ describe('copyMove', ()=>{
975
+ it('returns 404 when destination space not found', async ()=>{
976
+ const req = baseReq({
977
+ method: 'MOVE',
978
+ dav: {
979
+ ...baseReq().dav,
980
+ copyMove: {
981
+ destination: '/unknown',
982
+ isMove: false,
983
+ overwrite: false
984
+ }
985
+ }
986
+ });
987
+ const res = makeRes();
988
+ const handler = service['webDAVHandler'];
989
+ jest.spyOn(handler, 'spaceEnv').mockResolvedValue(null);
990
+ await service.copyMove(req, res);
991
+ expect(res.statusCode).toBe(_common.HttpStatus.NOT_FOUND);
992
+ expect(res.body).toBe('/unknown');
993
+ });
994
+ it('aborts when evaluateIfHeaders fails', async ()=>{
995
+ const req = baseReq({
996
+ method: 'COPY',
997
+ dav: {
998
+ ...baseReq().dav,
999
+ copyMove: {
1000
+ destination: '/dst',
1001
+ isMove: false,
1002
+ overwrite: true
1003
+ },
1004
+ ifHeaders: [
1005
+ {
1006
+ path: '/dst',
1007
+ token: {
1008
+ value: 'bad',
1009
+ mustMatch: true
1010
+ }
1011
+ }
1012
+ ]
1013
+ }
1014
+ });
1015
+ const res = makeRes();
1016
+ const handler = service['webDAVHandler'];
1017
+ jest.spyOn(handler, 'spaceEnv').mockResolvedValue({
1018
+ ...req.space,
1019
+ url: '/dst',
1020
+ realPath: '/real/dst',
1021
+ dbFile: {
1022
+ path: 'dst'
1023
+ }
1024
+ });
1025
+ const spy = jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(false);
1026
+ const result = await service.copyMove(req, res);
1027
+ expect(spy).toHaveBeenCalled();
1028
+ expect(result).toBeUndefined();
1029
+ });
1030
+ it('aborts with 412 when destination If-Header haveLock mismatches', async ()=>{
1031
+ const handler = service['webDAVHandler'];
1032
+ const dstSpace = {
1033
+ ...baseReq().space,
1034
+ url: '/dst',
1035
+ realPath: '/real/dst',
1036
+ dbFile: {
1037
+ path: 'dst'
1038
+ }
1039
+ };
1040
+ jest.spyOn(handler, 'spaceEnv').mockResolvedValue(dstSpace);
1041
+ _files.isPathExists.mockResolvedValue(true);
1042
+ filesLockManager.getLocksByPath.mockResolvedValue([
1043
+ {}
1044
+ ]); // there is a lock, but mustMatch=false -> mismatch
1045
+ const req = baseReq({
1046
+ method: 'COPY',
1047
+ dav: {
1048
+ ...baseReq().dav,
1049
+ copyMove: {
1050
+ destination: '/dst',
1051
+ isMove: false,
1052
+ overwrite: true
1053
+ },
1054
+ ifHeaders: [
1055
+ {
1056
+ path: '/dst',
1057
+ haveLock: {
1058
+ mustMatch: false
1059
+ }
1060
+ }
1061
+ ]
1062
+ }
1063
+ });
1064
+ const res = makeRes();
1065
+ const result = await service.copyMove(req, res);
1066
+ expect(result).toBeUndefined();
1067
+ expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
1068
+ expect(res.body).toBe('If header condition failed');
1069
+ });
1070
+ it('returns 204 when destination existed; 201 when not', async ()=>{
1071
+ const handler = service['webDAVHandler'];
1072
+ jest.spyOn(handler, 'spaceEnv').mockResolvedValue({
1073
+ ...baseReq().space,
1074
+ url: '/dst',
1075
+ realPath: '/real/dst',
1076
+ dbFile: {
1077
+ path: 'dst'
1078
+ }
1079
+ });
1080
+ jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(true);
1081
+ _files.isPathExists.mockResolvedValueOnce(true);
1082
+ const req1 = baseReq({
1083
+ method: 'MOVE',
1084
+ dav: {
1085
+ ...baseReq().dav,
1086
+ copyMove: {
1087
+ destination: '/dst',
1088
+ isMove: true,
1089
+ overwrite: true
1090
+ }
1091
+ }
1092
+ });
1093
+ const res1 = makeRes();
1094
+ await service.copyMove(req1, res1);
1095
+ expect(res1.statusCode).toBe(_common.HttpStatus.NO_CONTENT);
1096
+ _files.isPathExists.mockResolvedValueOnce(false);
1097
+ const req2 = baseReq({
1098
+ method: 'COPY',
1099
+ dav: {
1100
+ ...baseReq().dav,
1101
+ copyMove: {
1102
+ destination: '/dst',
1103
+ isMove: false,
1104
+ overwrite: false
1105
+ }
1106
+ }
1107
+ });
1108
+ const res2 = makeRes();
1109
+ await service.copyMove(req2, res2);
1110
+ expect(res2.statusCode).toBe(_common.HttpStatus.CREATED);
1111
+ });
1112
+ it('delegates errors to handleError', async ()=>{
1113
+ const handler = service['webDAVHandler'];
1114
+ jest.spyOn(handler, 'spaceEnv').mockResolvedValue({
1115
+ ...baseReq().space,
1116
+ url: '/dst',
1117
+ realPath: '/real/dst',
1118
+ dbFile: {
1119
+ path: 'dst'
1120
+ }
1121
+ });
1122
+ jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(true);
1123
+ // Chain 1) LockConflict without lockroot (fallback to dbFilePath) then 2) unexpected error
1124
+ const { LockConflict } = jest.requireActual('../../files/models/file-lock-error');
1125
+ filesManager.copyMove.mockRejectedValueOnce(new LockConflict({
1126
+ dbFilePath: 'dst'
1127
+ })).mockRejectedValueOnce(new Error('copy failed'));
1128
+ const req = baseReq({
1129
+ method: 'COPY',
1130
+ dav: {
1131
+ ...baseReq().dav,
1132
+ copyMove: {
1133
+ destination: '/dst',
1134
+ isMove: false,
1135
+ overwrite: true
1136
+ }
1137
+ }
1138
+ });
1139
+ // 1) LockConflict => DAV_ERROR_RES(423) using e.lock.dbFilePath
1140
+ const res1 = makeRes();
1141
+ const logSpy = jest.spyOn(service['logger'], 'error').mockImplementation(()=>undefined);
1142
+ const result1 = await service.copyMove(req, res1);
1143
+ expect(result1).toBe(res1);
1144
+ expect(res1.statusCode).toBe(_common.HttpStatus.LOCKED);
1145
+ // 2) Unexpected error => HttpException 500 and log contains " -> /dst"
1146
+ const res2 = makeRes();
1147
+ try {
1148
+ await service.copyMove(req, res2);
1149
+ expect(true).toBe(false); // should not reach
1150
+ } catch (e) {
1151
+ expect(e).toBeInstanceOf(_common.HttpException);
1152
+ expect(e.getStatus()).toBe(_common.HttpStatus.INTERNAL_SERVER_ERROR);
1153
+ }
1154
+ // Verify the log message includes an arrow to the destination (toUrl)
1155
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(' -> /dst'));
1156
+ });
1157
+ it('returns early when evaluateIfHeaders returns false (explicit spy)', async ()=>{
1158
+ const req = baseReq({
1159
+ method: 'COPY',
1160
+ dav: {
1161
+ ...baseReq().dav,
1162
+ copyMove: {
1163
+ destination: '/dst',
1164
+ isMove: false,
1165
+ overwrite: true
1166
+ },
1167
+ ifHeaders: [
1168
+ {
1169
+ path: '/dst'
1170
+ }
1171
+ ]
1172
+ }
1173
+ });
1174
+ const res = makeRes();
1175
+ const handler = service['webDAVHandler'];
1176
+ // Ensure destination space resolves so we reach the evaluateIfHeaders call
1177
+ jest.spyOn(handler, 'spaceEnv').mockResolvedValue({
1178
+ ...req.space,
1179
+ url: '/dst',
1180
+ realPath: '/real/dst',
1181
+ dbFile: {
1182
+ path: 'dst'
1183
+ }
1184
+ });
1185
+ _files.isPathExists.mockResolvedValue(true);
1186
+ // Decorator is no-op (mocked), so copyMove calls evaluateIfHeaders once for destination: force it to return false
1187
+ const spy = jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(false);
1188
+ const result = await service.copyMove(req, res);
1189
+ expect(spy).toHaveBeenCalledTimes(1);
1190
+ expect(result).toBeUndefined();
1191
+ expect(filesManager.copyMove).not.toHaveBeenCalled();
1192
+ });
1193
+ });
1194
+ describe('evaluateIfHeaders', ()=>{
1195
+ it('returns true when no headers', async ()=>{
1196
+ const req = baseReq({
1197
+ dav: {
1198
+ ...baseReq().dav,
1199
+ ifHeaders: undefined
1200
+ }
1201
+ });
1202
+ const res = makeRes();
1203
+ const ok = await service.evaluateIfHeaders(req, res);
1204
+ expect(ok).toBe(true);
1205
+ });
1206
+ const negativeIfHeaderCases = [
1207
+ {
1208
+ name: 'haveLock mismatch',
1209
+ dav: {
1210
+ ifHeaders: [
1211
+ {
1212
+ haveLock: {
1213
+ mustMatch: true
1214
+ }
1215
+ }
1216
+ ]
1217
+ },
1218
+ setup: async ()=>{
1219
+ // No lock present => match=false, mustMatch=true => mismatch
1220
+ ;
1221
+ _paths.dbFileFromSpace.mockReturnValue({
1222
+ path: 'file.txt',
1223
+ spaceId: 1,
1224
+ inTrash: false
1225
+ });
1226
+ filesLockManager.getLocksByPath.mockResolvedValue([]);
1227
+ }
1228
+ },
1229
+ {
1230
+ name: 'haveLock mismatch when locks exist but mustMatch=false',
1231
+ dav: {
1232
+ ifHeaders: [
1233
+ {
1234
+ haveLock: {
1235
+ mustMatch: false
1236
+ }
1237
+ }
1238
+ ]
1239
+ },
1240
+ setup: async ()=>{
1241
+ // Lock present => match=true, mustMatch=false => mismatch
1242
+ ;
1243
+ _paths.dbFileFromSpace.mockReturnValue({
1244
+ path: 'dst',
1245
+ spaceId: 1,
1246
+ inTrash: false
1247
+ });
1248
+ filesLockManager.getLocksByPath.mockResolvedValue([
1249
+ {}
1250
+ ]);
1251
+ }
1252
+ },
1253
+ {
1254
+ name: 'token not found',
1255
+ dav: {
1256
+ ifHeaders: [
1257
+ {
1258
+ token: {
1259
+ value: 'missing',
1260
+ mustMatch: true
1261
+ }
1262
+ }
1263
+ ]
1264
+ },
1265
+ setup: async ()=>{
1266
+ filesLockManager.getLockByToken.mockResolvedValue(null);
1267
+ }
1268
+ },
1269
+ {
1270
+ name: 'etag mismatch',
1271
+ dav: {
1272
+ ifHeaders: [
1273
+ {
1274
+ etag: {
1275
+ value: 'W/"bad"',
1276
+ mustMatch: true
1277
+ }
1278
+ }
1279
+ ]
1280
+ },
1281
+ setup: async ()=>{
1282
+ ;
1283
+ _files.isPathExists.mockResolvedValue(true);
1284
+ }
1285
+ }
1286
+ ];
1287
+ it.each(negativeIfHeaderCases)('fails with 412 on %s', async ({ dav, setup })=>{
1288
+ await setup();
1289
+ const req = baseReq({
1290
+ dav: {
1291
+ ...baseReq().dav,
1292
+ ...dav
1293
+ }
1294
+ });
1295
+ const res = makeRes();
1296
+ const ok = await service.evaluateIfHeaders(req, res);
1297
+ expect(ok).toBe(false);
1298
+ expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
1299
+ });
1300
+ it('returns true when one condition matches', async ()=>{
1301
+ ;
1302
+ _files.isPathExists.mockResolvedValue(true);
1303
+ const req = baseReq({
1304
+ dav: {
1305
+ ...baseReq().dav,
1306
+ ifHeaders: [
1307
+ {
1308
+ etag: {
1309
+ value: 'W/"bad"',
1310
+ mustMatch: true
1311
+ }
1312
+ },
1313
+ {
1314
+ etag: {
1315
+ value: 'W/"etag"',
1316
+ mustMatch: true
1317
+ }
1318
+ } // this should pass
1319
+ ]
1320
+ }
1321
+ });
1322
+ const res = makeRes();
1323
+ const ok = await service.evaluateIfHeaders(req, res);
1324
+ expect(ok).toBe(true);
1325
+ expect(res.statusCode).toBeUndefined();
1326
+ });
1327
+ it('fails with 412 on token url mismatch', async ()=>{
1328
+ const req = baseReq({
1329
+ dav: {
1330
+ ...baseReq().dav,
1331
+ ifHeaders: [
1332
+ {
1333
+ path: '/dav/other',
1334
+ token: {
1335
+ value: 'opaquetoken:xyz',
1336
+ mustMatch: true
1337
+ }
1338
+ }
1339
+ ]
1340
+ }
1341
+ });
1342
+ const res = makeRes();
1343
+ // Space for the explicit path
1344
+ const handler = service['webDAVHandler'];
1345
+ jest.spyOn(handler, 'spaceEnv').mockResolvedValue({
1346
+ ...req.space,
1347
+ url: '/dav/other',
1348
+ realPath: '/real/other',
1349
+ dbFile: {
1350
+ path: 'other'
1351
+ }
1352
+ });
1353
+ filesLockManager.getLockByToken.mockResolvedValue({
1354
+ davLock: {
1355
+ lockroot: '/dav/url'
1356
+ }
1357
+ }); // not a parent of /dav/other
1358
+ const ok = await service.evaluateIfHeaders(req, res);
1359
+ expect(ok).toBe(false);
1360
+ expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
1361
+ });
1362
+ it('fails with 412 and logs error when haveLock lookup throws', async ()=>{
1363
+ const req = baseReq({
1364
+ dav: {
1365
+ ...baseReq().dav,
1366
+ ifHeaders: [
1367
+ {
1368
+ path: '/dav/url',
1369
+ haveLock: {
1370
+ mustMatch: true
1371
+ }
1372
+ }
1373
+ ]
1374
+ }
1375
+ });
1376
+ const res = makeRes();
1377
+ const handler = service['webDAVHandler'];
1378
+ jest.spyOn(handler, 'spaceEnv').mockResolvedValue(req.space);
1379
+ filesLockManager.getLocksByPath.mockRejectedValue(new Error('boom'));
1380
+ const ok = await service.evaluateIfHeaders(req, res);
1381
+ expect(ok).toBe(false);
1382
+ expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
1383
+ expect(res.body).toBe('If header condition failed');
1384
+ });
1385
+ it('returns true when token exists and path matches lockroot', async ()=>{
1386
+ const req = baseReq({
1387
+ dav: {
1388
+ ...baseReq().dav,
1389
+ ifHeaders: [
1390
+ {
1391
+ path: '/dav/url',
1392
+ token: {
1393
+ value: 'opaquetoken:good',
1394
+ mustMatch: true
1395
+ }
1396
+ }
1397
+ ]
1398
+ }
1399
+ });
1400
+ const res = makeRes();
1401
+ const handler = service['webDAVHandler'];
1402
+ jest.spyOn(handler, 'spaceEnv').mockResolvedValue(req.space);
1403
+ filesLockManager.getLockByToken.mockResolvedValue({
1404
+ davLock: {
1405
+ lockroot: '/dav/url'
1406
+ }
1407
+ });
1408
+ const ok = await service.evaluateIfHeaders(req, res);
1409
+ expect(ok).toBe(true);
1410
+ expect(res.statusCode).toBeUndefined();
1411
+ });
1412
+ it('returns false without setting status when If-Header path cannot be resolved', async ()=>{
1413
+ const req = baseReq({
1414
+ dav: {
1415
+ ...baseReq().dav,
1416
+ ifHeaders: [
1417
+ {
1418
+ path: '/dav/missing'
1419
+ }
1420
+ ]
1421
+ }
1422
+ });
1423
+ const res = makeRes();
1424
+ const handler = service['webDAVHandler'];
1425
+ jest.spyOn(handler, 'spaceEnv').mockResolvedValue(null);
1426
+ const ok = await service.evaluateIfHeaders(req, res);
1427
+ expect(ok).toBe(false);
1428
+ expect(res.statusCode).toBeUndefined();
1429
+ expect(res.body).toBeUndefined();
1430
+ });
1431
+ it('fails with 412 on etag when resource does not exist (null etag)', async ()=>{
1432
+ ;
1433
+ _files.isPathExists.mockResolvedValue(false);
1434
+ const req = baseReq({
1435
+ dav: {
1436
+ ...baseReq().dav,
1437
+ ifHeaders: [
1438
+ {
1439
+ etag: {
1440
+ value: 'W/"etag"',
1441
+ mustMatch: true
1442
+ }
1443
+ }
1444
+ ]
1445
+ }
1446
+ });
1447
+ const res = makeRes();
1448
+ const ok = await service.evaluateIfHeaders(req, res);
1449
+ expect(ok).toBe(false);
1450
+ expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
1451
+ });
1452
+ });
1453
+ describe('lockRefresh', ()=>{
1454
+ const refresh400Cases = [
1455
+ {
1456
+ name: 'more than one or zero tokens in If header',
1457
+ dav: {
1458
+ body: undefined,
1459
+ ifHeaders: []
1460
+ },
1461
+ expectMsg: 'Expected a lock token'
1462
+ },
1463
+ {
1464
+ name: 'token extraction fails',
1465
+ dav: {
1466
+ body: undefined,
1467
+ ifHeaders: [
1468
+ {
1469
+ notAToken: true
1470
+ }
1471
+ ]
1472
+ },
1473
+ expectMsg: 'Unable to extract token'
1474
+ }
1475
+ ];
1476
+ it.each(refresh400Cases)('returns 400 when %s', async ({ dav, expectMsg })=>{
1477
+ const req = baseReq({
1478
+ dav: {
1479
+ ...baseReq().dav,
1480
+ ...dav
1481
+ }
1482
+ });
1483
+ const res = makeRes();
1484
+ await service.lockRefresh(req, res, req.space.dbFile.path);
1485
+ expect(res.statusCode).toBe(_common.HttpStatus.BAD_REQUEST);
1486
+ expect(res.body).toContain(expectMsg);
1487
+ });
1488
+ it('returns 412 when token not found or not matching URL', async ()=>{
1489
+ const req = baseReq({
1490
+ dav: {
1491
+ ...baseReq().dav,
1492
+ body: undefined,
1493
+ ifHeaders: [
1494
+ {
1495
+ token: {
1496
+ value: 'opaquetoken:missing',
1497
+ mustMatch: true
1498
+ }
1499
+ }
1500
+ ]
1501
+ }
1502
+ });
1503
+ const res = makeRes();
1504
+ filesLockManager.isLockedWithToken.mockResolvedValue(null);
1505
+ jest.spyOn(_ifheader, 'extractOneToken').mockReturnValue('opaquetoken:missing');
1506
+ await service.lockRefresh(req, res, req.space.dbFile.path);
1507
+ expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
1508
+ });
1509
+ it('returns 403 when owner mismatch', async ()=>{
1510
+ const req = baseReq({
1511
+ dav: {
1512
+ ...baseReq().dav,
1513
+ body: undefined,
1514
+ ifHeaders: [
1515
+ {
1516
+ token: {
1517
+ value: 'opaquetoken:abc',
1518
+ mustMatch: true
1519
+ }
1520
+ }
1521
+ ]
1522
+ }
1523
+ });
1524
+ const res = makeRes();
1525
+ jest.spyOn(_ifheader, 'extractOneToken').mockReturnValue('opaquetoken:abc');
1526
+ filesLockManager.isLockedWithToken.mockResolvedValue({
1527
+ owner: {
1528
+ id: 2
1529
+ }
1530
+ });
1531
+ await service.lockRefresh(req, res, baseReq().space.dbFile.path);
1532
+ expect(res.statusCode).toBe(_common.HttpStatus.FORBIDDEN);
1533
+ expect(res.body).toBe('Lock token does not match owner');
1534
+ });
1535
+ it('returns 200 and XML body on success', async ()=>{
1536
+ const req = baseReq({
1537
+ dav: {
1538
+ ...baseReq().dav,
1539
+ body: undefined,
1540
+ lock: {
1541
+ ...baseReq().dav.lock,
1542
+ timeout: 120
1543
+ },
1544
+ ifHeaders: [
1545
+ {
1546
+ token: {
1547
+ value: 'opaquetoken:abc',
1548
+ mustMatch: true
1549
+ }
1550
+ }
1551
+ ]
1552
+ }
1553
+ });
1554
+ const res = makeRes();
1555
+ jest.spyOn(_ifheader, 'extractOneToken').mockReturnValue('opaquetoken:abc');
1556
+ filesLockManager.isLockedWithToken.mockResolvedValue({
1557
+ owner: {
1558
+ id: 1
1559
+ },
1560
+ davLock: {
1561
+ lockroot: '/dav/url'
1562
+ }
1563
+ });
1564
+ await service.lockRefresh(req, res, req.space.dbFile.path);
1565
+ expect(filesLockManager.refreshLockTimeout).toHaveBeenCalled();
1566
+ expect(res.statusCode).toBe(_common.HttpStatus.OK);
1567
+ expect(res.contentType).toContain('application/xml');
1568
+ expect(typeof res.body).toBe('string');
1569
+ });
1570
+ });
1571
+ describe('handleError integration', ()=>{
1572
+ it('maps LockConflict to 423 Locked via DAV_ERROR_RES', async ()=>{
1573
+ // simulate LockConflict during PUT
1574
+ const { LockConflict } = jest.requireActual('../../files/models/file-lock-error');
1575
+ filesManager.saveStream.mockRejectedValue(new LockConflict({
1576
+ dbFilePath: 'file.txt',
1577
+ davLock: {
1578
+ lockroot: '/dav/url'
1579
+ }
1580
+ }));
1581
+ const req = baseReq({
1582
+ method: 'PUT'
1583
+ });
1584
+ const res = makeRes();
1585
+ const result = await service.put(req, res);
1586
+ expect(result).toBe(res);
1587
+ expect(res.statusCode).toBe(_common.HttpStatus.LOCKED);
1588
+ expect(typeof res.body).toBe('string');
1589
+ });
1590
+ it('maps FileError to its httpCode and message', async ()=>{
1591
+ const { FileError } = jest.requireActual('../../files/models/file-error');
1592
+ filesManager.delete.mockRejectedValue(new FileError(409, 'conflict happened'));
1593
+ const req = baseReq({
1594
+ method: 'DELETE'
1595
+ });
1596
+ const res = makeRes();
1597
+ const result = await service.delete(req, res);
1598
+ expect(result).toBe(res);
1599
+ expect(res.statusCode).toBe(409);
1600
+ expect(res.body).toBe('conflict happened');
1601
+ });
1602
+ it('throws 500 HttpException for unexpected errors', async ()=>{
1603
+ const req = baseReq({
1604
+ method: 'PUT'
1605
+ });
1606
+ const res = makeRes();
1607
+ filesManager.saveStream.mockRejectedValue(new Error('unexpected'));
1608
+ try {
1609
+ await service.put(req, res);
1610
+ // If we reach this line, the test should fail
1611
+ expect(true).toBe(false);
1612
+ } catch (e) {
1613
+ expect(e).toBeInstanceOf(_common.HttpException);
1614
+ expect(e.getStatus()).toBe(_common.HttpStatus.INTERNAL_SERVER_ERROR);
1615
+ }
1616
+ });
1617
+ });
39
1618
  });
40
1619
 
41
1620
  //# sourceMappingURL=webdav-methods.service.spec.js.map