@sync-in/server 1.9.6 → 1.10.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.
- package/CHANGELOG.md +29 -4
- package/environment/environment.dist.yaml +15 -5
- package/package.json +13 -14
- package/server/app.bootstrap.js +1 -1
- package/server/app.bootstrap.js.map +1 -1
- package/server/app.constants.js +3 -2
- package/server/app.constants.js.map +1 -1
- package/server/applications/files/constants/cache.js +2 -5
- package/server/applications/files/constants/cache.js.map +1 -1
- package/server/applications/files/constants/files.js +4 -0
- package/server/applications/files/constants/files.js.map +1 -1
- package/server/applications/files/constants/operations.js +4 -0
- package/server/applications/files/constants/operations.js.map +1 -1
- package/server/applications/files/constants/routes.js +1 -26
- package/server/applications/files/constants/routes.js.map +1 -1
- package/server/applications/files/files.config.js +15 -39
- package/server/applications/files/files.config.js.map +1 -1
- package/server/applications/files/files.controller.js +4 -4
- package/server/applications/files/files.controller.js.map +1 -1
- package/server/applications/files/files.module.js +12 -9
- package/server/applications/files/files.module.js.map +1 -1
- package/server/applications/files/interfaces/file-lock.interface.js.map +1 -1
- package/server/applications/files/interfaces/file-props.interface.js.map +1 -1
- package/server/applications/files/modules/collabora-online/collabora-online-environment.decorator.js +32 -0
- package/server/applications/files/modules/collabora-online/collabora-online-environment.decorator.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online-manager.service.js +280 -0
- package/server/applications/files/modules/collabora-online/collabora-online-manager.service.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online-manager.service.spec.js +552 -0
- package/server/applications/files/modules/collabora-online/collabora-online-manager.service.spec.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.config.js +40 -0
- package/server/applications/files/modules/collabora-online/collabora-online.config.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.constants.js +110 -0
- package/server/applications/files/modules/collabora-online/collabora-online.constants.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.controller.js +128 -0
- package/server/applications/files/modules/collabora-online/collabora-online.controller.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.controller.spec.js +47 -0
- package/server/applications/files/modules/collabora-online/collabora-online.controller.spec.js.map +1 -0
- package/server/applications/files/{interfaces/only-office-config.interface.js → modules/collabora-online/collabora-online.dtos.js} +1 -1
- package/server/applications/files/modules/collabora-online/collabora-online.dtos.js.map +1 -0
- package/server/applications/files/{guards/files-only-office.guard.js → modules/collabora-online/collabora-online.guard.js} +7 -21
- package/server/applications/files/modules/collabora-online/collabora-online.guard.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.guard.spec.js +86 -0
- package/server/applications/files/modules/collabora-online/collabora-online.guard.spec.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.interface.js +10 -0
- package/server/applications/files/modules/collabora-online/collabora-online.interface.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.module.js +41 -0
- package/server/applications/files/modules/collabora-online/collabora-online.module.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.routes.js +35 -0
- package/server/applications/files/modules/collabora-online/collabora-online.routes.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.strategy.js +59 -0
- package/server/applications/files/modules/collabora-online/collabora-online.strategy.js.map +1 -0
- package/server/applications/files/modules/collabora-online/collabora-online.utils.js +28 -0
- package/server/applications/files/modules/collabora-online/collabora-online.utils.js.map +1 -0
- package/server/applications/files/{decorators → modules/only-office}/only-office-environment.decorator.js +5 -5
- package/server/applications/files/modules/only-office/only-office-environment.decorator.js.map +1 -0
- package/server/applications/files/{services/files-only-office-manager.service.js → modules/only-office/only-office-manager.service.js} +101 -97
- package/server/applications/files/modules/only-office/only-office-manager.service.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office-manager.service.spec.js +477 -0
- package/server/applications/files/modules/only-office/only-office-manager.service.spec.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.config.js +51 -0
- package/server/applications/files/modules/only-office/only-office.config.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.constants.js +417 -0
- package/server/applications/files/modules/only-office/only-office.constants.js.map +1 -0
- package/server/applications/files/{files-only-office.controller.js → modules/only-office/only-office.controller.js} +35 -52
- package/server/applications/files/modules/only-office/only-office.controller.js.map +1 -0
- package/server/applications/files/{files-only-office.controller.spec.js → modules/only-office/only-office.controller.spec.js} +24 -21
- package/server/applications/files/modules/only-office/only-office.controller.spec.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.dtos.js +10 -0
- package/server/applications/files/modules/only-office/only-office.dtos.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.guard.js +40 -0
- package/server/applications/files/modules/only-office/only-office.guard.js.map +1 -0
- package/server/applications/files/{guards/files-only-office.guard.spec.js → modules/only-office/only-office.guard.spec.js} +15 -21
- package/server/applications/files/modules/only-office/only-office.guard.spec.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.interface.js +10 -0
- package/server/applications/files/modules/only-office/only-office.interface.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.module.js +41 -0
- package/server/applications/files/modules/only-office/only-office.module.js.map +1 -0
- package/server/applications/files/modules/only-office/only-office.routes.js +45 -0
- package/server/applications/files/modules/only-office/only-office.routes.js.map +1 -0
- package/server/applications/files/{guards/files-only-office.strategy.js → modules/only-office/only-office.strategy.js} +11 -11
- package/server/applications/files/modules/only-office/only-office.strategy.js.map +1 -0
- package/server/applications/files/services/files-lock-manager.service.js +25 -33
- package/server/applications/files/services/files-lock-manager.service.js.map +1 -1
- package/server/applications/files/services/files-manager.service.js +17 -16
- package/server/applications/files/services/files-manager.service.js.map +1 -1
- package/server/applications/files/services/files-methods.service.js +2 -2
- package/server/applications/files/services/files-methods.service.js.map +1 -1
- package/server/applications/files/services/files-methods.service.spec.js +3 -1
- package/server/applications/files/services/files-methods.service.spec.js.map +1 -1
- package/server/applications/files/services/files-scheduler.service.js +2 -2
- package/server/applications/files/services/files-scheduler.service.js.map +1 -1
- package/server/applications/files/utils/files.js +10 -2
- package/server/applications/files/utils/files.js.map +1 -1
- package/server/applications/links/constants/routes.js +5 -0
- package/server/applications/links/constants/routes.js.map +1 -1
- package/server/applications/links/interfaces/link-space.interface.js.map +1 -1
- package/server/applications/links/links.controller.js +25 -5
- package/server/applications/links/links.controller.js.map +1 -1
- package/server/applications/links/services/links-manager.service.js +43 -21
- package/server/applications/links/services/links-manager.service.js.map +1 -1
- package/server/applications/links/services/links-manager.service.spec.js +4 -3
- package/server/applications/links/services/links-manager.service.spec.js.map +1 -1
- package/server/applications/links/services/links-queries.service.js +9 -2
- package/server/applications/links/services/links-queries.service.js.map +1 -1
- package/server/applications/shares/interfaces/share-link.interface.js.map +1 -1
- package/server/applications/shares/services/shares-manager.service.js +3 -0
- package/server/applications/shares/services/shares-manager.service.js.map +1 -1
- package/server/applications/shares/services/shares-manager.service.spec.js +2 -1
- package/server/applications/shares/services/shares-manager.service.spec.js.map +1 -1
- package/server/applications/shares/services/shares-queries.service.js +1 -0
- package/server/applications/shares/services/shares-queries.service.js.map +1 -1
- package/server/applications/spaces/constants/spaces.js +2 -2
- package/server/applications/spaces/constants/spaces.js.map +1 -1
- package/server/applications/spaces/decorators/space-override-permission.decorator.js +18 -0
- package/server/applications/spaces/decorators/space-override-permission.decorator.js.map +1 -0
- package/server/applications/spaces/guards/space.guard.js +40 -33
- package/server/applications/spaces/guards/space.guard.js.map +1 -1
- package/server/applications/spaces/guards/space.guard.spec.js +10 -15
- package/server/applications/spaces/guards/space.guard.spec.js.map +1 -1
- package/server/applications/webdav/constants/webdav.js +4 -0
- package/server/applications/webdav/constants/webdav.js.map +1 -1
- package/server/applications/webdav/guards/webdav-protocol.guard.js +9 -8
- package/server/applications/webdav/guards/webdav-protocol.guard.js.map +1 -1
- package/server/applications/webdav/guards/webdav-protocol.guard.spec.js +1 -1
- package/server/applications/webdav/guards/webdav-protocol.guard.spec.js.map +1 -1
- package/server/applications/webdav/interfaces/webdav.interface.js.map +1 -1
- package/server/applications/webdav/services/webdav-methods.service.js +40 -17
- package/server/applications/webdav/services/webdav-methods.service.js.map +1 -1
- package/server/applications/webdav/services/webdav-methods.service.spec.js +2157 -1289
- package/server/applications/webdav/services/webdav-methods.service.spec.js.map +1 -1
- package/server/applications/webdav/utils/webdav.js +8 -4
- package/server/applications/webdav/utils/webdav.js.map +1 -1
- package/server/applications/webdav/webdav.controller.js +4 -4
- package/server/applications/webdav/webdav.controller.js.map +1 -1
- package/server/authentication/guards/auth-token-access.guard.js +8 -3
- package/server/authentication/guards/auth-token-access.guard.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.js +1 -1
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.js.map +1 -1
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.spec.js +350 -4
- package/server/authentication/services/auth-methods/auth-method-two-fa.service.spec.js.map +1 -1
- package/server/configuration/config.environment.js +5 -1
- package/server/configuration/config.environment.js.map +1 -1
- package/server/configuration/config.interfaces.js.map +1 -1
- package/static/3rdpartylicenses.txt +507 -507
- package/static/assets/pdfjs/build/pdf.mjs +93 -33
- package/static/assets/pdfjs/build/pdf.mjs.map +1 -1
- package/static/assets/pdfjs/build/pdf.sandbox.mjs +3 -3
- package/static/assets/pdfjs/build/pdf.sandbox.mjs.map +1 -1
- package/static/assets/pdfjs/build/pdf.worker.mjs +166 -54
- package/static/assets/pdfjs/build/pdf.worker.mjs.map +1 -1
- package/static/assets/pdfjs/version +1 -1
- package/static/assets/pdfjs/web/images/checkmark.svg +5 -0
- package/static/assets/pdfjs/web/images/pages_closeButton.svg +3 -0
- package/static/assets/pdfjs/web/images/pages_selected.svg +7 -0
- package/static/assets/pdfjs/web/images/pages_viewArrow.svg +3 -0
- package/static/assets/pdfjs/web/images/pages_viewButton.svg +3 -0
- package/static/assets/pdfjs/web/locale/be/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/bs/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/cs/viewer.ftl +4 -6
- package/static/assets/pdfjs/web/locale/cy/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/da/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/de/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/dsb/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/el/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/en-CA/viewer.ftl +6 -2
- package/static/assets/pdfjs/web/locale/en-GB/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/en-US/viewer.ftl +82 -17
- package/static/assets/pdfjs/web/locale/eo/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/es-AR/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/es-CL/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/es-ES/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/es-MX/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/eu/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/fi/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/fr/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/fur/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/fy-NL/viewer.ftl +3 -5
- package/static/assets/pdfjs/web/locale/gn/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/he/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/hr/viewer.ftl +66 -0
- package/static/assets/pdfjs/web/locale/hsb/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/hu/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/hy-AM/viewer.ftl +3 -8
- package/static/assets/pdfjs/web/locale/ia/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/id/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/is/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/it/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/ja/viewer.ftl +0 -14
- package/static/assets/pdfjs/web/locale/ka/viewer.ftl +4 -6
- package/static/assets/pdfjs/web/locale/kab/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/kk/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/ko/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/nb-NO/viewer.ftl +1 -3
- package/static/assets/pdfjs/web/locale/nl/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/nn-NO/viewer.ftl +4 -2
- package/static/assets/pdfjs/web/locale/pa-IN/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/pl/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/pt-BR/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/pt-PT/viewer.ftl +35 -0
- package/static/assets/pdfjs/web/locale/rm/viewer.ftl +0 -5
- package/static/assets/pdfjs/web/locale/ro/viewer.ftl +4 -6
- package/static/assets/pdfjs/web/locale/ru/viewer.ftl +3 -5
- package/static/assets/pdfjs/web/locale/sk/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/sl/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/sq/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/sv-SE/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/tg/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/th/viewer.ftl +2 -2
- package/static/assets/pdfjs/web/locale/tr/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/vi/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/zh-CN/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/locale/zh-TW/viewer.ftl +0 -2
- package/static/assets/pdfjs/web/viewer.css +1778 -835
- package/static/assets/pdfjs/web/viewer.html +167 -86
- package/static/assets/pdfjs/web/viewer.mjs +1106 -801
- package/static/assets/pdfjs/web/viewer.mjs.map +1 -1
- package/static/chunk-27V66YJV.js +2 -0
- package/static/{chunk-BVKDW5XO.js → chunk-27Z3SYRL.js} +1 -1
- package/static/{chunk-HLKZCMKV.js → chunk-2RWLNKZH.js} +1 -1
- package/static/chunk-2YQ4SX3A.js +13 -0
- package/static/{chunk-GENTF6JM.js → chunk-3JYMJQYT.js} +1 -1
- package/static/chunk-3QTROEHV.js +1 -0
- package/static/{chunk-C5T7RZSD.js → chunk-3RPUQ22U.js} +1 -1
- package/static/{chunk-EPDWJEPD.js → chunk-3WZ6F3LC.js} +1 -1
- package/static/chunk-3ZLBVUCX.js +2 -0
- package/static/{chunk-SF6Q6VRC.js → chunk-45AZ6ZML.js} +1 -1
- package/static/chunk-46TJLPJY.js +1 -0
- package/static/chunk-4NIYCYRS.js +2 -0
- package/static/{chunk-GLPKRULI.js → chunk-4TPFERL6.js} +1 -1
- package/static/{chunk-KAAFVHYE.js → chunk-5O66CLTD.js} +1 -1
- package/static/chunk-6OEOADR6.js +1 -0
- package/static/chunk-6WMXMIE4.js +1 -0
- package/static/{chunk-QKMN3S4M.js → chunk-7VRYTDX4.js} +1 -1
- package/static/{chunk-Z2KBIZ5D.js → chunk-ARS47O5X.js} +1 -1
- package/static/chunk-B6HQYQYG.js +1 -0
- package/static/chunk-BCN4T5DO.js +2 -0
- package/static/{chunk-7HL5Z6PF.js → chunk-CCZWPM7Q.js} +1 -1
- package/static/{chunk-DU4Q4RWJ.js → chunk-CMNMPG6Z.js} +1 -1
- package/static/{chunk-BX3QZ7IL.js → chunk-CSVPAZHK.js} +1 -1
- package/static/{chunk-BJARRIS6.js → chunk-D55YR5X7.js} +4 -4
- package/static/{chunk-NHMYAVJK.js → chunk-D5FQ72R4.js} +1 -1
- package/static/{chunk-7QYALK5T.js → chunk-DGCVA6BM.js} +1 -1
- package/static/{chunk-IBC7CFBQ.js → chunk-DVCN3P7Q.js} +1 -1
- package/static/chunk-E32J777S.js +5 -0
- package/static/{chunk-FEQUP26G.js → chunk-FIUF2JM4.js} +1 -1
- package/static/{chunk-ODAQRAPO.js → chunk-G3PL6YX3.js} +1 -1
- package/static/chunk-G7RZN7HN.js +1 -0
- package/static/{chunk-IIKL33TV.js → chunk-GQHXYX6Z.js} +1 -1
- package/static/{chunk-5HYSNQR4.js → chunk-GWRAGN3M.js} +1 -1
- package/static/{chunk-ANH4VNOS.js → chunk-GXWGB7WO.js} +1 -1
- package/static/{chunk-25PWAXTJ.js → chunk-HGODIZTV.js} +1 -1
- package/static/{chunk-X5UDV4ZB.js → chunk-HZAB6F4Q.js} +1 -1
- package/static/chunk-I3FR3A45.js +1 -0
- package/static/{chunk-JYHTSSKW.js → chunk-I5SPA4G2.js} +1 -1
- package/static/{chunk-DQAQUSVW.js → chunk-IMFO2MI7.js} +1 -1
- package/static/{chunk-AYYJZMBE.js → chunk-JNTNMIUH.js} +1 -1
- package/static/chunk-JRXG43AA.js +2 -0
- package/static/{chunk-3AR5VNJE.js → chunk-KAUCN24H.js} +1 -1
- package/static/{chunk-3WS72A6C.js → chunk-KDUAB76O.js} +1 -1
- package/static/chunk-KPOQLDWF.js +1 -0
- package/static/{chunk-UO7ATVQG.js → chunk-KWFELZTM.js} +1 -1
- package/static/{chunk-2CAAJBRO.js → chunk-L3BIP4AA.js} +1 -1
- package/static/{chunk-DK2LAJEL.js → chunk-LGIVVJDD.js} +1 -1
- package/static/{chunk-5ATJIR5S.js → chunk-LNLBIJZD.js} +1 -1
- package/static/chunk-LTJNLOX2.js +1 -0
- package/static/{chunk-GRV44RYI.js → chunk-LZUHREOF.js} +1 -1
- package/static/{chunk-XIQXRSZ2.js → chunk-NIR4YE2E.js} +1 -1
- package/static/{chunk-TOCCCZP2.js → chunk-NJJURHX4.js} +1 -1
- package/static/chunk-NNZWSNAW.js +1 -0
- package/static/chunk-NWKBB7J4.js +1 -0
- package/static/chunk-O3YLAEVE.js +3 -0
- package/static/chunk-OUHCDDT6.js +1 -0
- package/static/{chunk-B4TDS6AQ.js → chunk-PDG7DOEF.js} +1 -1
- package/static/chunk-POUWUMC4.js +1 -0
- package/static/{chunk-NQCKX2AD.js → chunk-PPJCVBJH.js} +1 -1
- package/static/{chunk-ZCOEP4O2.js → chunk-PQZLR4P3.js} +1 -1
- package/static/chunk-PVYVY3GD.js +1 -0
- package/static/chunk-Q5X5TPAG.js +1 -0
- package/static/{chunk-LFAQLJZK.js → chunk-QHJT5H4M.js} +1 -1
- package/static/{chunk-D6QWQHWE.js → chunk-R4VMWCM5.js} +1 -1
- package/static/{chunk-O233BXWK.js → chunk-R7PLNX75.js} +1 -1
- package/static/chunk-RJULB733.js +1 -0
- package/static/{chunk-E5C4QRNQ.js → chunk-RNVPQQKT.js} +5 -5
- package/static/chunk-RTNEBRKJ.js +1 -0
- package/static/{chunk-GYYJ4FWN.js → chunk-S3TTWPQA.js} +1 -1
- package/static/{chunk-4GBA6EJ4.js → chunk-SDJNZULP.js} +1 -1
- package/static/chunk-SNOOCDJD.js +1 -0
- package/static/chunk-T42BV6TR.js +1 -0
- package/static/{chunk-5KVI243T.js → chunk-TNCKNU6I.js} +1 -1
- package/static/{chunk-OVUMPMVM.js → chunk-ULSPQ3HP.js} +1 -1
- package/static/{chunk-5NFH4E2B.js → chunk-UOK3LKSX.js} +1 -1
- package/static/{chunk-CHMDM2ZW.js → chunk-VD5JHSDS.js} +1 -1
- package/static/{chunk-2F42MZQ5.js → chunk-XBKCQCBI.js} +1 -1
- package/static/{chunk-2U5VKTML.js → chunk-XEWLBWFF.js} +1 -1
- package/static/{chunk-FSGT46LM.js → chunk-XTVNHFKX.js} +1 -1
- package/static/chunk-ZCSHU3D7.js +1 -0
- package/static/{chunk-QUUQOBTF.js → chunk-ZEJLIGAY.js} +1 -1
- package/static/{chunk-7H5O4BLV.js → chunk-ZHOE5VEY.js} +1 -1
- package/static/chunk-ZOMRIN3G.js +2 -0
- package/static/index.html +2 -2
- package/static/main-YKDNJ7LK.js +11 -0
- package/static/{styles-S5HVK4H5.css → styles-XLLEY5Y3.css} +1 -1
- package/server/applications/files/constants/only-office.js +0 -531
- package/server/applications/files/constants/only-office.js.map +0 -1
- package/server/applications/files/decorators/only-office-environment.decorator.js.map +0 -1
- package/server/applications/files/files-only-office.controller.js.map +0 -1
- package/server/applications/files/files-only-office.controller.spec.js.map +0 -1
- package/server/applications/files/guards/files-only-office.guard.js.map +0 -1
- package/server/applications/files/guards/files-only-office.guard.spec.js.map +0 -1
- package/server/applications/files/guards/files-only-office.strategy.js.map +0 -1
- package/server/applications/files/interfaces/only-office-config.interface.js.map +0 -1
- package/server/applications/files/services/files-only-office-manager.service.js.map +0 -1
- package/server/applications/files/services/files-only-office-manager.service.spec.js +0 -58
- package/server/applications/files/services/files-only-office-manager.service.spec.js.map +0 -1
- package/static/chunk-42L6C5MT.js +0 -1
- package/static/chunk-4ZKAVMB4.js +0 -1
- package/static/chunk-5GIWZKNS.js +0 -1
- package/static/chunk-5WCQBTXW.js +0 -1
- package/static/chunk-B2A4HNDC.js +0 -1
- package/static/chunk-BSB4VROD.js +0 -2
- package/static/chunk-CUC7R6C2.js +0 -1
- package/static/chunk-DHFQIFOF.js +0 -1
- package/static/chunk-DRHPEERW.js +0 -2
- package/static/chunk-FCGTI42I.js +0 -1
- package/static/chunk-FCR5AEHR.js +0 -3
- package/static/chunk-HB5DC7RJ.js +0 -1
- package/static/chunk-ITVA26X2.js +0 -2
- package/static/chunk-KWKZN53T.js +0 -1
- package/static/chunk-LBXOAKBD.js +0 -1
- package/static/chunk-LZKI5P5T.js +0 -1
- package/static/chunk-MGMDT4VN.js +0 -1
- package/static/chunk-MWUUM2NK.js +0 -13
- package/static/chunk-MYM43ENO.js +0 -1
- package/static/chunk-NAH4V2R6.js +0 -2
- package/static/chunk-PCFH5HCI.js +0 -2
- package/static/chunk-Q6B4OVER.js +0 -5
- package/static/chunk-QV5LQKTS.js +0 -1
- package/static/chunk-S4UTSOPV.js +0 -1
- package/static/chunk-SRBOO7AO.js +0 -1
- package/static/chunk-VZPCXSRG.js +0 -2
- package/static/chunk-XKEBQNQJ.js +0 -1
- package/static/chunk-YYTDPI5S.js +0 -1
- package/static/main-ODUA232E.js +0 -11
- /package/static/assets/pdfjs/web/images/{toolbarButton-sidebarToggle.svg → toolbarButton-viewsManagerToggle.svg} +0 -0
|
@@ -8,6 +8,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
8
8
|
});
|
|
9
9
|
const _common = require("@nestjs/common");
|
|
10
10
|
const _testing = require("@nestjs/testing");
|
|
11
|
+
const _fileerror = require("../../files/models/file-error");
|
|
12
|
+
const _filelockerror = require("../../files/models/file-lock-error");
|
|
11
13
|
const _fileslockmanagerservice = require("../../files/services/files-lock-manager.service");
|
|
12
14
|
const _filesmanagerservice = require("../../files/services/files-manager.service");
|
|
13
15
|
const _files = require("../../files/utils/files");
|
|
@@ -59,10 +61,13 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
59
61
|
}
|
|
60
62
|
return newObj;
|
|
61
63
|
}
|
|
64
|
+
// Mock external dependencies
|
|
62
65
|
jest.mock('../../files/utils/files', ()=>({
|
|
63
|
-
isPathExists: jest.fn(),
|
|
66
|
+
isPathExists: jest.fn().mockReturnValue(false),
|
|
67
|
+
isPathIsDir: jest.fn(),
|
|
68
|
+
fileName: jest.fn().mockReturnValue('fileName'),
|
|
64
69
|
dirName: jest.fn(),
|
|
65
|
-
genEtag: jest.fn().mockReturnValue('W/"etag"')
|
|
70
|
+
genEtag: jest.fn().mockReturnValue('W/"etag-123"')
|
|
66
71
|
}));
|
|
67
72
|
jest.mock('../../spaces/utils/permissions', ()=>({
|
|
68
73
|
haveSpaceEnvPermissions: jest.fn()
|
|
@@ -77,12 +82,13 @@ jest.mock('../../spaces/utils/paths', ()=>{
|
|
|
77
82
|
jest.mock('../decorators/if-header.decorator', ()=>({
|
|
78
83
|
IfHeaderDecorator: ()=>(_target, _key, _desc)=>undefined
|
|
79
84
|
}));
|
|
80
|
-
describe(
|
|
85
|
+
describe('WebDAVMethods', ()=>{
|
|
81
86
|
let service;
|
|
82
87
|
let filesManager;
|
|
83
88
|
let filesLockManager;
|
|
84
89
|
let webDAVSpaces;
|
|
85
|
-
|
|
90
|
+
// Helper to create a mocked response object
|
|
91
|
+
const createMockResponse = ()=>{
|
|
86
92
|
const res = {
|
|
87
93
|
statusCode: undefined,
|
|
88
94
|
body: undefined,
|
|
@@ -107,56 +113,64 @@ describe(_webdavmethodsservice.WebDAVMethods.name, ()=>{
|
|
|
107
113
|
};
|
|
108
114
|
return res;
|
|
109
115
|
};
|
|
110
|
-
|
|
116
|
+
// Helper to create a base request object
|
|
117
|
+
const createBaseRequest = (overrides = {})=>({
|
|
111
118
|
method: 'GET',
|
|
112
119
|
user: {
|
|
113
120
|
id: 1,
|
|
114
|
-
login: 'user
|
|
121
|
+
login: 'test-user',
|
|
122
|
+
fullName: 'Test User',
|
|
123
|
+
email: 'test-user@sync-in.com'
|
|
115
124
|
},
|
|
116
125
|
dav: {
|
|
117
|
-
url: '/webdav/
|
|
126
|
+
url: '/webdav/test/file.txt',
|
|
118
127
|
depth: '0',
|
|
119
128
|
httpVersion: 'HTTP/1.1',
|
|
120
|
-
body: '<
|
|
129
|
+
body: '<lockinfo/>',
|
|
121
130
|
lock: {
|
|
122
131
|
timeout: 60,
|
|
123
132
|
lockscope: 'exclusive',
|
|
124
|
-
owner: 'user
|
|
125
|
-
token: '
|
|
126
|
-
}
|
|
133
|
+
owner: 'test-user',
|
|
134
|
+
token: 'opaquelocktoken:abc123'
|
|
135
|
+
},
|
|
136
|
+
ifHeaders: []
|
|
127
137
|
},
|
|
128
138
|
space: {
|
|
129
|
-
id:
|
|
130
|
-
alias: '
|
|
131
|
-
url: '/
|
|
132
|
-
realPath: '/real/path/file.txt',
|
|
139
|
+
id: 1,
|
|
140
|
+
alias: 'test-space',
|
|
141
|
+
url: '/webdav/test/file.txt',
|
|
142
|
+
realPath: '/real/path/to/file.txt',
|
|
133
143
|
inSharesList: false,
|
|
134
144
|
dbFile: {
|
|
135
|
-
path: 'file.txt'
|
|
145
|
+
path: 'file.txt',
|
|
146
|
+
spaceId: 1,
|
|
147
|
+
inTrash: false
|
|
136
148
|
}
|
|
137
149
|
},
|
|
138
150
|
...overrides
|
|
139
151
|
});
|
|
140
|
-
|
|
152
|
+
beforeEach(async ()=>{
|
|
153
|
+
// Initialize mocks
|
|
141
154
|
filesManager = {
|
|
142
155
|
sendFileFromSpace: jest.fn(),
|
|
143
|
-
mkFile: jest.fn()
|
|
156
|
+
mkFile: jest.fn(),
|
|
144
157
|
saveStream: jest.fn(),
|
|
145
|
-
delete: jest.fn()
|
|
146
|
-
touch: jest.fn()
|
|
147
|
-
mkDir: jest.fn()
|
|
148
|
-
copyMove: jest.fn()
|
|
158
|
+
delete: jest.fn(),
|
|
159
|
+
touch: jest.fn(),
|
|
160
|
+
mkDir: jest.fn(),
|
|
161
|
+
copyMove: jest.fn()
|
|
149
162
|
};
|
|
150
163
|
filesLockManager = {
|
|
151
164
|
create: jest.fn(),
|
|
152
165
|
isLockedWithToken: jest.fn(),
|
|
153
|
-
removeLock: jest.fn()
|
|
166
|
+
removeLock: jest.fn(),
|
|
154
167
|
browseLocks: jest.fn(),
|
|
155
168
|
browseParentChildLocks: jest.fn(),
|
|
156
|
-
checkConflicts: jest.fn()
|
|
169
|
+
checkConflicts: jest.fn(),
|
|
157
170
|
getLocksByPath: jest.fn(),
|
|
158
171
|
getLockByToken: jest.fn(),
|
|
159
|
-
refreshLockTimeout: jest.fn()
|
|
172
|
+
refreshLockTimeout: jest.fn(),
|
|
173
|
+
genDAVToken: jest.fn().mockReturnValue('opaquelocktoken:new-token')
|
|
160
174
|
};
|
|
161
175
|
webDAVSpaces = {
|
|
162
176
|
propfind: jest.fn(),
|
|
@@ -183,1436 +197,2290 @@ describe(_webdavmethodsservice.WebDAVMethods.name, ()=>{
|
|
|
183
197
|
'fatal'
|
|
184
198
|
]);
|
|
185
199
|
service = module.get(_webdavmethodsservice.WebDAVMethods);
|
|
186
|
-
|
|
187
|
-
beforeEach(()=>{
|
|
200
|
+
// Reset global mocks
|
|
188
201
|
jest.clearAllMocks();
|
|
189
|
-
_files.isPathExists.
|
|
190
|
-
_files.dirName.mockReturnValue('/real/path');
|
|
202
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
203
|
+
_files.dirName.mockReturnValue('/real/path/to');
|
|
191
204
|
_permissions.haveSpaceEnvPermissions.mockReturnValue(true);
|
|
192
205
|
});
|
|
193
206
|
afterEach(()=>{
|
|
194
207
|
jest.restoreAllMocks();
|
|
195
208
|
});
|
|
196
|
-
|
|
197
|
-
|
|
209
|
+
describe('Service initialization', ()=>{
|
|
210
|
+
it('should be defined', ()=>{
|
|
211
|
+
expect(service).toBeDefined();
|
|
212
|
+
expect(service).toBeInstanceOf(_webdavmethodsservice.WebDAVMethods);
|
|
213
|
+
});
|
|
198
214
|
});
|
|
199
215
|
describe('headOrGet', ()=>{
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
it('returns 403 when repository is not allowed', async ()=>{
|
|
218
|
-
const req = baseReq({
|
|
219
|
-
space: {
|
|
220
|
-
...baseReq().space,
|
|
221
|
-
inSharesList: true
|
|
222
|
-
}
|
|
216
|
+
describe('Success cases', ()=>{
|
|
217
|
+
it('should stream file when repository is FILES and not in shares list', async ()=>{
|
|
218
|
+
const req = createBaseRequest();
|
|
219
|
+
const res = createMockResponse();
|
|
220
|
+
const streamable = {
|
|
221
|
+
stream: 'file-content'
|
|
222
|
+
};
|
|
223
|
+
const sendFile = {
|
|
224
|
+
checks: jest.fn().mockResolvedValue(undefined),
|
|
225
|
+
stream: jest.fn().mockResolvedValue(streamable)
|
|
226
|
+
};
|
|
227
|
+
filesManager.sendFileFromSpace.mockReturnValue(sendFile);
|
|
228
|
+
const result = await service.headOrGet(req, res, _spaces.SPACE_REPOSITORY.FILES);
|
|
229
|
+
expect(filesManager.sendFileFromSpace).toHaveBeenCalledWith(req.space);
|
|
230
|
+
expect(sendFile.checks).toHaveBeenCalledTimes(1);
|
|
231
|
+
expect(sendFile.stream).toHaveBeenCalledWith(req, res);
|
|
232
|
+
expect(result).toBe(streamable);
|
|
223
233
|
});
|
|
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
234
|
});
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
235
|
+
describe('Error cases', ()=>{
|
|
236
|
+
it('should return 403 when repository is not FILES', async ()=>{
|
|
237
|
+
const req = createBaseRequest();
|
|
238
|
+
const res = createMockResponse();
|
|
239
|
+
await service.headOrGet(req, res, 'OTHER_REPO');
|
|
240
|
+
expect(res.statusCode).toBe(_common.HttpStatus.FORBIDDEN);
|
|
241
|
+
expect(res.body).toBe('Not allowed on this resource');
|
|
242
|
+
});
|
|
243
|
+
it('should return 403 when resource is in shares list', async ()=>{
|
|
244
|
+
const req = createBaseRequest({
|
|
245
|
+
space: {
|
|
246
|
+
...createBaseRequest().space,
|
|
247
|
+
inSharesList: true
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
const res = createMockResponse();
|
|
251
|
+
await service.headOrGet(req, res, _spaces.SPACE_REPOSITORY.FILES);
|
|
252
|
+
expect(res.statusCode).toBe(_common.HttpStatus.FORBIDDEN);
|
|
253
|
+
expect(res.body).toBe('Not allowed on this resource');
|
|
254
|
+
});
|
|
255
|
+
it('should handle errors from sendFile.checks', async ()=>{
|
|
256
|
+
const req = createBaseRequest();
|
|
257
|
+
const res = createMockResponse();
|
|
258
|
+
const error = new Error('File check failed');
|
|
259
|
+
const sendFile = {
|
|
260
|
+
checks: jest.fn().mockRejectedValue(error),
|
|
261
|
+
stream: jest.fn()
|
|
262
|
+
};
|
|
263
|
+
filesManager.sendFileFromSpace.mockReturnValue(sendFile);
|
|
264
|
+
jest.spyOn(service, 'handleError').mockReturnValue('error-handled');
|
|
265
|
+
const result = await service.headOrGet(req, res, _spaces.SPACE_REPOSITORY.FILES);
|
|
266
|
+
expect(result).toBe('error-handled');
|
|
267
|
+
});
|
|
268
|
+
it('should handle errors from sendFile.stream', async ()=>{
|
|
269
|
+
const req = createBaseRequest();
|
|
270
|
+
const res = createMockResponse();
|
|
271
|
+
const error = new Error('Stream failed');
|
|
272
|
+
const sendFile = {
|
|
273
|
+
checks: jest.fn().mockResolvedValue(undefined),
|
|
274
|
+
stream: jest.fn().mockRejectedValue(error)
|
|
275
|
+
};
|
|
276
|
+
filesManager.sendFileFromSpace.mockReturnValue(sendFile);
|
|
277
|
+
jest.spyOn(service, 'handleError').mockReturnValue('error-handled');
|
|
278
|
+
const result = await service.headOrGet(req, res, _spaces.SPACE_REPOSITORY.FILES);
|
|
279
|
+
expect(result).toBe('error-handled');
|
|
280
|
+
});
|
|
242
281
|
});
|
|
243
282
|
});
|
|
244
283
|
describe('lock', ()=>{
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
284
|
+
describe('Lock refresh (without body)', ()=>{
|
|
285
|
+
it('should return 400 if resource does not exist for lock refresh', async ()=>{
|
|
286
|
+
;
|
|
287
|
+
_files.isPathExists.mockResolvedValue(false);
|
|
288
|
+
const req = createBaseRequest({
|
|
289
|
+
dav: {
|
|
290
|
+
...createBaseRequest().dav,
|
|
291
|
+
body: undefined
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
const res = createMockResponse();
|
|
295
|
+
await service.lock(req, res);
|
|
296
|
+
expect(res.statusCode).toBe(_common.HttpStatus.BAD_REQUEST);
|
|
297
|
+
expect(res.body).toBe('Lock refresh must specify an existing resource');
|
|
298
|
+
});
|
|
299
|
+
it('should delegate to lockRefresh when resource exists and no body', async ()=>{
|
|
300
|
+
;
|
|
301
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
302
|
+
const req = createBaseRequest({
|
|
303
|
+
dav: {
|
|
304
|
+
...createBaseRequest().dav,
|
|
305
|
+
body: undefined
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
const res = createMockResponse();
|
|
309
|
+
const lockRefreshSpy = jest.spyOn(service, 'lockRefresh').mockResolvedValue('refresh-ok');
|
|
310
|
+
const result = await service.lock(req, res);
|
|
311
|
+
expect(lockRefreshSpy).toHaveBeenCalledWith(req, res, req.space.dbFile.path);
|
|
312
|
+
expect(result).toBe('refresh-ok');
|
|
253
313
|
});
|
|
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
314
|
});
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
315
|
+
describe('Lock creation on existing resource', ()=>{
|
|
316
|
+
it('should create lock successfully and return 200', async ()=>{
|
|
317
|
+
;
|
|
318
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
319
|
+
const req = createBaseRequest();
|
|
320
|
+
const res = createMockResponse();
|
|
321
|
+
filesLockManager.create.mockImplementation(async (_user, _dbFile, _app, _depth, options, _timeout)=>{
|
|
322
|
+
return [
|
|
323
|
+
true,
|
|
324
|
+
{
|
|
325
|
+
owner: {
|
|
326
|
+
fullName: 'LockOwner',
|
|
327
|
+
email: 'lock-owner@sync-in.com'
|
|
328
|
+
},
|
|
329
|
+
dbFilePath: _dbFile?.path,
|
|
330
|
+
options: {
|
|
331
|
+
lockRoot: options.lockRoot,
|
|
332
|
+
lockToken: options.lockToken,
|
|
333
|
+
lockScope: options.lockScope,
|
|
334
|
+
lockInfo: options.lockInfo
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
];
|
|
338
|
+
});
|
|
339
|
+
await service.lock(req, res);
|
|
340
|
+
expect(filesLockManager.create).toHaveBeenCalledTimes(1);
|
|
341
|
+
expect(res.statusCode).toBe(_common.HttpStatus.OK);
|
|
342
|
+
expect(res.contentType).toBe('application/xml; charset=utf-8');
|
|
343
|
+
expect(res.headers['lock-token']).toContain('opaquelocktoken:new-token');
|
|
344
|
+
expect(res.body).toBeDefined();
|
|
345
|
+
expect(typeof res.body).toBe('string');
|
|
267
346
|
});
|
|
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
347
|
});
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
348
|
+
describe('Lock creation on non-existent resource', ()=>{
|
|
349
|
+
it('should return 403 when user lacks ADD permission', async ()=>{
|
|
350
|
+
;
|
|
351
|
+
_files.isPathExists.mockResolvedValue(false);
|
|
352
|
+
_permissions.haveSpaceEnvPermissions.mockReturnValue(false);
|
|
353
|
+
const req = createBaseRequest();
|
|
354
|
+
const res = createMockResponse();
|
|
355
|
+
await service.lock(req, res);
|
|
356
|
+
expect(res.statusCode).toBe(_common.HttpStatus.FORBIDDEN);
|
|
357
|
+
expect(res.body).toBe('You are not allowed to do this action');
|
|
358
|
+
expect(filesLockManager.create).not.toHaveBeenCalled();
|
|
359
|
+
});
|
|
360
|
+
it('should return 409 when parent directory does not exist', async ()=>{
|
|
361
|
+
;
|
|
362
|
+
_files.isPathExists.mockResolvedValueOnce(false) // resource
|
|
363
|
+
.mockResolvedValueOnce(false) // parent
|
|
364
|
+
;
|
|
365
|
+
_permissions.haveSpaceEnvPermissions.mockReturnValue(true);
|
|
366
|
+
_files.dirName.mockReturnValue('/real/path/missing');
|
|
367
|
+
const req = createBaseRequest();
|
|
368
|
+
const res = createMockResponse();
|
|
369
|
+
await service.lock(req, res);
|
|
370
|
+
expect(res.statusCode).toBe(_common.HttpStatus.CONFLICT);
|
|
371
|
+
expect(res.body).toBe('Parent must exists');
|
|
372
|
+
expect(filesLockManager.create).not.toHaveBeenCalled();
|
|
373
|
+
});
|
|
374
|
+
it('should create empty file and lock, return 201', async ()=>{
|
|
375
|
+
;
|
|
376
|
+
_files.isPathExists.mockResolvedValueOnce(false) // resource
|
|
377
|
+
.mockResolvedValueOnce(true); // parent exists
|
|
378
|
+
const req = createBaseRequest();
|
|
379
|
+
const res = createMockResponse();
|
|
380
|
+
filesLockManager.create.mockImplementation(async (_user, _dbFile, _app, _depth, options)=>{
|
|
381
|
+
return [
|
|
382
|
+
true,
|
|
383
|
+
{
|
|
384
|
+
owner: {
|
|
385
|
+
fullName: 'LockOwner',
|
|
386
|
+
email: 'lock-owner@sync-in.com'
|
|
387
|
+
},
|
|
388
|
+
dbFilePath: _dbFile?.path,
|
|
389
|
+
options: {
|
|
390
|
+
lockRoot: options.lockRoot,
|
|
391
|
+
lockToken: options.lockToken,
|
|
392
|
+
lockScope: options.lockScope,
|
|
393
|
+
lockInfo: options.lockInfo
|
|
394
|
+
}
|
|
318
395
|
}
|
|
319
|
-
|
|
320
|
-
|
|
396
|
+
];
|
|
397
|
+
});
|
|
398
|
+
await service.lock(req, res);
|
|
399
|
+
expect(filesManager.mkFile).toHaveBeenCalledWith(req.user, req.space, false, false, false);
|
|
400
|
+
expect(res.statusCode).toBe(_common.HttpStatus.CREATED);
|
|
401
|
+
expect(res.headers['lock-token']).toContain('opaquelocktoken:new-token');
|
|
321
402
|
});
|
|
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
403
|
});
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return [
|
|
338
|
-
true,
|
|
404
|
+
describe('Lock conflict', ()=>{
|
|
405
|
+
it('should return 423 when lock conflict occurs', async ()=>{
|
|
406
|
+
;
|
|
407
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
408
|
+
const req = createBaseRequest();
|
|
409
|
+
const res = createMockResponse();
|
|
410
|
+
filesLockManager.create.mockResolvedValue([
|
|
411
|
+
false,
|
|
339
412
|
{
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
timeout: _timeout
|
|
413
|
+
owner: {
|
|
414
|
+
fullName: 'LockOwner',
|
|
415
|
+
email: 'lock-owner@sync-in.com'
|
|
416
|
+
},
|
|
417
|
+
dbFilePath: 'file.txt',
|
|
418
|
+
options: {
|
|
419
|
+
lockRoot: '/webdav/locked/resource'
|
|
348
420
|
}
|
|
349
421
|
}
|
|
350
|
-
];
|
|
422
|
+
]);
|
|
423
|
+
await service.lock(req, res);
|
|
424
|
+
expect(res.statusCode).toBe(_common.HttpStatus.LOCKED);
|
|
425
|
+
expect(res.contentType).toBe('application/xml; charset=utf-8');
|
|
351
426
|
});
|
|
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
427
|
});
|
|
375
428
|
});
|
|
376
429
|
describe('unlock', ()=>{
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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();
|
|
430
|
+
describe('Success cases', ()=>{
|
|
431
|
+
it('should unlock resource and return 204', async ()=>{
|
|
432
|
+
;
|
|
433
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
434
|
+
filesLockManager.isLockedWithToken.mockResolvedValue({
|
|
435
|
+
owner: {
|
|
436
|
+
id: 1,
|
|
437
|
+
login: 'test-user'
|
|
438
|
+
},
|
|
439
|
+
key: 'lock-key-123'
|
|
440
|
+
});
|
|
441
|
+
const req = createBaseRequest();
|
|
442
|
+
const res = createMockResponse();
|
|
443
|
+
await service.unlock(req, res);
|
|
444
|
+
expect(filesLockManager.removeLock).toHaveBeenCalledWith('lock-key-123');
|
|
445
|
+
expect(res.statusCode).toBe(_common.HttpStatus.NO_CONTENT);
|
|
446
|
+
});
|
|
411
447
|
});
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
448
|
+
describe('Error cases', ()=>{
|
|
449
|
+
it('should return 404 when resource does not exist', async ()=>{
|
|
450
|
+
;
|
|
451
|
+
_files.isPathExists.mockResolvedValue(false);
|
|
452
|
+
const req = createBaseRequest();
|
|
453
|
+
const res = createMockResponse();
|
|
454
|
+
await service.unlock(req, res);
|
|
455
|
+
expect(res.statusCode).toBe(_common.HttpStatus.NOT_FOUND);
|
|
456
|
+
expect(res.body).toBe(req.dav.url);
|
|
457
|
+
});
|
|
458
|
+
it('should return 409 when lock token does not exist', async ()=>{
|
|
459
|
+
;
|
|
460
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
461
|
+
filesLockManager.isLockedWithToken.mockResolvedValue(null);
|
|
462
|
+
const req = createBaseRequest();
|
|
463
|
+
const res = createMockResponse();
|
|
464
|
+
await service.unlock(req, res);
|
|
465
|
+
expect(filesLockManager.isLockedWithToken).toHaveBeenCalledWith(req.dav.lock.token, req.space.dbFile.path);
|
|
466
|
+
expect(res.statusCode).toBe(_common.HttpStatus.CONFLICT);
|
|
467
|
+
});
|
|
468
|
+
it('should return 403 when lock owner is different user', async ()=>{
|
|
469
|
+
;
|
|
470
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
471
|
+
filesLockManager.isLockedWithToken.mockResolvedValue({
|
|
472
|
+
owner: {
|
|
473
|
+
id: 999,
|
|
474
|
+
login: 'other-user'
|
|
475
|
+
},
|
|
476
|
+
key: 'lock-key-456'
|
|
477
|
+
});
|
|
478
|
+
const req = createBaseRequest();
|
|
479
|
+
const res = createMockResponse();
|
|
480
|
+
await service.unlock(req, res);
|
|
481
|
+
expect(res.statusCode).toBe(_common.HttpStatus.FORBIDDEN);
|
|
482
|
+
expect(res.body).toBe('Token was created by another user');
|
|
483
|
+
expect(filesLockManager.removeLock).not.toHaveBeenCalled();
|
|
420
484
|
});
|
|
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
485
|
});
|
|
427
486
|
});
|
|
428
487
|
describe('propfind', ()=>{
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
488
|
+
describe('Base cases', ()=>{
|
|
489
|
+
it('should return 404 when resource does not exist in FILES repository', async ()=>{
|
|
490
|
+
;
|
|
491
|
+
_files.isPathExists.mockResolvedValue(false);
|
|
492
|
+
const req = createBaseRequest({
|
|
493
|
+
dav: {
|
|
494
|
+
...createBaseRequest().dav,
|
|
495
|
+
propfindMode: 'prop'
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
const res = createMockResponse();
|
|
499
|
+
const result = await service.propfind(req, res, _spaces.SPACE_REPOSITORY.FILES);
|
|
500
|
+
expect(result).toBe(res);
|
|
501
|
+
expect(res.statusCode).toBe(_common.HttpStatus.NOT_FOUND);
|
|
502
|
+
expect(res.body).toBe(req.dav.url);
|
|
503
|
+
});
|
|
504
|
+
it('should return multistatus with property names in PROPNAME mode', async ()=>{
|
|
505
|
+
;
|
|
506
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
507
|
+
const req = createBaseRequest({
|
|
508
|
+
dav: {
|
|
509
|
+
...createBaseRequest().dav,
|
|
510
|
+
propfindMode: 'propname'
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
const res = createMockResponse();
|
|
514
|
+
webDAVSpaces.propfind.mockImplementation(async function*() {
|
|
515
|
+
yield {
|
|
516
|
+
href: '/webdav/test/file.txt',
|
|
517
|
+
name: 'file.txt'
|
|
518
|
+
};
|
|
519
|
+
});
|
|
520
|
+
await service.propfind(req, res, _spaces.SPACE_REPOSITORY.FILES);
|
|
521
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
522
|
+
expect(res.contentType).toContain('application/xml');
|
|
523
|
+
expect(typeof res.body).toBe('string');
|
|
524
|
+
expect(res.body).toContain('/webdav/test/file.txt');
|
|
525
|
+
});
|
|
526
|
+
it('should return multistatus with property values in PROP mode', async ()=>{
|
|
527
|
+
;
|
|
528
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
529
|
+
const req = createBaseRequest({
|
|
530
|
+
dav: {
|
|
531
|
+
...createBaseRequest().dav,
|
|
532
|
+
propfindMode: 'prop',
|
|
533
|
+
body: {
|
|
534
|
+
propfind: {
|
|
535
|
+
prop: {
|
|
536
|
+
[_webdav.STANDARD_PROPS[0]]: ''
|
|
537
|
+
}
|
|
479
538
|
}
|
|
480
539
|
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
const res = createMockResponse();
|
|
543
|
+
webDAVSpaces.propfind.mockImplementation(async function*() {
|
|
544
|
+
yield {
|
|
545
|
+
href: '/webdav/test/file.txt',
|
|
546
|
+
name: 'file.txt',
|
|
547
|
+
getlastmodified: 'Mon, 01 Jan 2024 00:00:00 GMT'
|
|
548
|
+
};
|
|
549
|
+
});
|
|
550
|
+
await service.propfind(req, res, _spaces.SPACE_REPOSITORY.FILES);
|
|
551
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
552
|
+
expect(res.contentType).toContain('application/xml');
|
|
553
|
+
expect(typeof res.body).toBe('string');
|
|
486
554
|
});
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
555
|
+
});
|
|
556
|
+
describe('Lock discovery', ()=>{
|
|
557
|
+
it('should collect locks with depth 0', async ()=>{
|
|
558
|
+
;
|
|
559
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
560
|
+
const req = createBaseRequest({
|
|
561
|
+
dav: {
|
|
562
|
+
...createBaseRequest().dav,
|
|
563
|
+
propfindMode: 'prop',
|
|
564
|
+
depth: _webdav.DEPTH.RESOURCE,
|
|
565
|
+
body: {
|
|
566
|
+
propfind: {
|
|
567
|
+
prop: {
|
|
568
|
+
[_webdav.LOCK_DISCOVERY_PROP]: ''
|
|
569
|
+
}
|
|
494
570
|
}
|
|
495
571
|
}
|
|
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
572
|
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
573
|
+
});
|
|
574
|
+
const res = createMockResponse();
|
|
575
|
+
webDAVSpaces.propfind.mockImplementation(async function*() {
|
|
576
|
+
yield {
|
|
577
|
+
href: '/webdav/test/file.txt',
|
|
578
|
+
name: 'file.txt'
|
|
579
|
+
};
|
|
580
|
+
});
|
|
581
|
+
filesLockManager.browseLocks.mockResolvedValue({
|
|
582
|
+
'file.txt': {
|
|
583
|
+
owner: {
|
|
584
|
+
fullName: 'LockOwner',
|
|
585
|
+
email: 'lock-owner@sync-in.com'
|
|
586
|
+
},
|
|
587
|
+
options: {
|
|
588
|
+
lockRoot: '/webdav/test/file.txt'
|
|
589
|
+
}
|
|
525
590
|
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
591
|
+
});
|
|
592
|
+
await service.propfind(req, res, _spaces.SPACE_REPOSITORY.FILES);
|
|
593
|
+
expect(filesLockManager.browseLocks).toHaveBeenCalledWith(req.space.dbFile);
|
|
594
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
595
|
+
});
|
|
596
|
+
it('should collect parent and child locks with depth infinity', async ()=>{
|
|
597
|
+
;
|
|
598
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
599
|
+
const req = createBaseRequest({
|
|
600
|
+
dav: {
|
|
601
|
+
...createBaseRequest().dav,
|
|
602
|
+
propfindMode: 'prop',
|
|
603
|
+
depth: 'infinity',
|
|
604
|
+
body: {
|
|
605
|
+
propfind: {
|
|
606
|
+
prop: {
|
|
607
|
+
[_webdav.LOCK_DISCOVERY_PROP]: ''
|
|
608
|
+
}
|
|
543
609
|
}
|
|
544
610
|
}
|
|
545
611
|
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
612
|
+
});
|
|
613
|
+
const res = createMockResponse();
|
|
614
|
+
webDAVSpaces.propfind.mockImplementation(async function*() {
|
|
615
|
+
yield {
|
|
616
|
+
href: '/webdav/test/file.txt',
|
|
617
|
+
name: 'file.txt'
|
|
618
|
+
};
|
|
619
|
+
});
|
|
620
|
+
filesLockManager.browseParentChildLocks.mockResolvedValue({
|
|
621
|
+
'file.txt': {
|
|
622
|
+
owner: {
|
|
623
|
+
fullName: 'LockOwner',
|
|
624
|
+
email: 'lock-owner@sync-in.com'
|
|
625
|
+
},
|
|
626
|
+
options: {
|
|
627
|
+
lockRoot: '/webdav/test/file.txt'
|
|
628
|
+
}
|
|
560
629
|
}
|
|
561
|
-
}
|
|
630
|
+
});
|
|
631
|
+
await service.propfind(req, res, _spaces.SPACE_REPOSITORY.FILES);
|
|
632
|
+
expect(filesLockManager.browseParentChildLocks).toHaveBeenCalledWith(req.space.dbFile);
|
|
633
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
634
|
+
});
|
|
635
|
+
it('should not collect locks for PROPNAME mode', async ()=>{
|
|
636
|
+
;
|
|
637
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
638
|
+
const req = createBaseRequest({
|
|
639
|
+
dav: {
|
|
640
|
+
...createBaseRequest().dav,
|
|
641
|
+
propfindMode: _webdav.PROPSTAT.PROPNAME
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
const res = createMockResponse();
|
|
645
|
+
webDAVSpaces.propfind.mockImplementation(async function*() {
|
|
646
|
+
yield {
|
|
647
|
+
href: '/webdav/test',
|
|
648
|
+
name: 'test'
|
|
649
|
+
};
|
|
650
|
+
});
|
|
651
|
+
await service.propfind(req, res, _spaces.SPACE_REPOSITORY.FILES);
|
|
652
|
+
expect(filesLockManager.browseLocks).not.toHaveBeenCalled();
|
|
653
|
+
expect(filesLockManager.browseParentChildLocks).not.toHaveBeenCalled();
|
|
654
|
+
});
|
|
655
|
+
it('should not collect locks for shares list', async ()=>{
|
|
656
|
+
;
|
|
657
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
658
|
+
const req = createBaseRequest({
|
|
659
|
+
space: {
|
|
660
|
+
...createBaseRequest().space,
|
|
661
|
+
inSharesList: true
|
|
662
|
+
},
|
|
663
|
+
dav: {
|
|
664
|
+
...createBaseRequest().dav,
|
|
665
|
+
propfindMode: 'prop',
|
|
666
|
+
body: {
|
|
667
|
+
propfind: {
|
|
668
|
+
prop: {
|
|
669
|
+
[_webdav.LOCK_DISCOVERY_PROP]: ''
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
const res = createMockResponse();
|
|
676
|
+
webDAVSpaces.propfind.mockImplementation(async function*() {
|
|
677
|
+
yield {
|
|
678
|
+
href: '/webdav/shares',
|
|
679
|
+
name: 'shares'
|
|
680
|
+
};
|
|
681
|
+
});
|
|
682
|
+
await service.propfind(req, res, _spaces.SPACE_REPOSITORY.FILES);
|
|
683
|
+
expect(filesLockManager.browseLocks).not.toHaveBeenCalled();
|
|
562
684
|
});
|
|
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
685
|
});
|
|
568
686
|
});
|
|
569
687
|
describe('put', ()=>{
|
|
570
|
-
|
|
571
|
-
{
|
|
572
|
-
existed
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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) {
|
|
688
|
+
describe('Success cases', ()=>{
|
|
689
|
+
it('should return 204 when updating existing file', async ()=>{
|
|
690
|
+
filesManager.saveStream.mockResolvedValue(true); // file existed
|
|
691
|
+
const req = createBaseRequest({
|
|
692
|
+
method: 'PUT'
|
|
693
|
+
});
|
|
694
|
+
const res = createMockResponse();
|
|
695
|
+
const result = await service.put(req, res);
|
|
696
|
+
expect(filesManager.saveStream).toHaveBeenCalledWith(req.user, req.space, req, expect.objectContaining({
|
|
697
|
+
dav: expect.any(Object)
|
|
698
|
+
}));
|
|
699
|
+
expect(res.statusCode).toBe(_common.HttpStatus.NO_CONTENT);
|
|
589
700
|
expect(res.headers['etag']).toBeDefined();
|
|
590
701
|
expect(result).toBe(res);
|
|
591
|
-
}
|
|
592
|
-
|
|
702
|
+
});
|
|
703
|
+
it('should return 201 when creating new file', async ()=>{
|
|
704
|
+
filesManager.saveStream.mockResolvedValue(false); // file didn't exist
|
|
705
|
+
const req = createBaseRequest({
|
|
706
|
+
method: 'PUT'
|
|
707
|
+
});
|
|
708
|
+
const res = createMockResponse();
|
|
709
|
+
const result = await service.put(req, res);
|
|
710
|
+
expect(res.statusCode).toBe(_common.HttpStatus.CREATED);
|
|
711
|
+
expect(res.headers['etag']).toBeDefined();
|
|
712
|
+
expect(result).toBe(res);
|
|
713
|
+
});
|
|
714
|
+
it('should extract and pass lock tokens from if-headers', async ()=>{
|
|
715
|
+
filesManager.saveStream.mockResolvedValue(true);
|
|
716
|
+
const req = createBaseRequest({
|
|
717
|
+
method: 'PUT',
|
|
718
|
+
dav: {
|
|
719
|
+
...createBaseRequest().dav,
|
|
720
|
+
ifHeaders: [
|
|
721
|
+
{
|
|
722
|
+
token: {
|
|
723
|
+
value: 'opaquelocktoken:xyz',
|
|
724
|
+
mustMatch: true
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
]
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
const res = createMockResponse();
|
|
731
|
+
jest.spyOn(_ifheader, 'extractAllTokens').mockReturnValue([
|
|
732
|
+
'opaquelocktoken:xyz'
|
|
733
|
+
]);
|
|
734
|
+
await service.put(req, res);
|
|
735
|
+
expect(filesManager.saveStream).toHaveBeenCalledWith(req.user, req.space, req, expect.objectContaining({
|
|
736
|
+
dav: expect.objectContaining({
|
|
737
|
+
lockTokens: [
|
|
738
|
+
'opaquelocktoken:xyz'
|
|
739
|
+
]
|
|
740
|
+
})
|
|
741
|
+
}));
|
|
742
|
+
});
|
|
593
743
|
});
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
744
|
+
describe('Error handling', ()=>{
|
|
745
|
+
it('should handle lock conflict error', async ()=>{
|
|
746
|
+
const lockError = new _filelockerror.LockConflict({
|
|
747
|
+
dbFilePath: 'file.txt',
|
|
748
|
+
options: {
|
|
749
|
+
lockRoot: '/webdav/locked'
|
|
750
|
+
}
|
|
751
|
+
}, 'Lock conflict');
|
|
752
|
+
filesManager.saveStream.mockRejectedValue(lockError);
|
|
753
|
+
const req = createBaseRequest({
|
|
754
|
+
method: 'PUT'
|
|
755
|
+
});
|
|
756
|
+
const res = createMockResponse();
|
|
757
|
+
await service.put(req, res);
|
|
758
|
+
expect(res.statusCode).toBe(_common.HttpStatus.LOCKED);
|
|
759
|
+
});
|
|
760
|
+
it('should handle file error', async ()=>{
|
|
761
|
+
const fileError = new _fileerror.FileError(409, 'File conflict');
|
|
762
|
+
filesManager.saveStream.mockRejectedValue(fileError);
|
|
763
|
+
const req = createBaseRequest({
|
|
764
|
+
method: 'PUT'
|
|
765
|
+
});
|
|
766
|
+
const res = createMockResponse();
|
|
767
|
+
await service.put(req, res);
|
|
768
|
+
expect(res.statusCode).toBe(409);
|
|
769
|
+
expect(res.body).toBe('File conflict');
|
|
770
|
+
});
|
|
771
|
+
it('should throw HttpException for unexpected errors', async ()=>{
|
|
772
|
+
const unexpectedError = new Error('Unexpected error');
|
|
773
|
+
filesManager.saveStream.mockRejectedValue(unexpectedError);
|
|
774
|
+
const req = createBaseRequest({
|
|
775
|
+
method: 'PUT'
|
|
776
|
+
});
|
|
777
|
+
const res = createMockResponse();
|
|
778
|
+
await expect(service.put(req, res)).rejects.toThrow(_common.HttpException);
|
|
779
|
+
});
|
|
605
780
|
});
|
|
606
781
|
});
|
|
607
782
|
describe('delete', ()=>{
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
783
|
+
describe('Success cases', ()=>{
|
|
784
|
+
it('should delete resource and return 204', async ()=>{
|
|
785
|
+
filesManager.delete.mockResolvedValue(undefined);
|
|
786
|
+
const req = createBaseRequest({
|
|
787
|
+
method: 'DELETE'
|
|
788
|
+
});
|
|
789
|
+
const res = createMockResponse();
|
|
790
|
+
const result = await service.delete(req, res);
|
|
791
|
+
expect(filesManager.delete).toHaveBeenCalledWith(req.user, req.space, expect.objectContaining({
|
|
792
|
+
lockTokens: expect.any(Array)
|
|
793
|
+
}));
|
|
794
|
+
expect(res.statusCode).toBe(_common.HttpStatus.NO_CONTENT);
|
|
795
|
+
expect(result).toBe(res);
|
|
796
|
+
});
|
|
797
|
+
it('should extract lock tokens from if-headers', async ()=>{
|
|
798
|
+
filesManager.delete.mockResolvedValue(undefined);
|
|
799
|
+
const req = createBaseRequest({
|
|
800
|
+
method: 'DELETE',
|
|
801
|
+
dav: {
|
|
802
|
+
...createBaseRequest().dav,
|
|
803
|
+
ifHeaders: [
|
|
804
|
+
{
|
|
805
|
+
token: {
|
|
806
|
+
value: 'opaquelocktoken:abc',
|
|
807
|
+
mustMatch: true
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
]
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
const res = createMockResponse();
|
|
814
|
+
jest.spyOn(_ifheader, 'extractAllTokens').mockReturnValue([
|
|
815
|
+
'opaquelocktoken:abc'
|
|
816
|
+
]);
|
|
817
|
+
await service.delete(req, res);
|
|
818
|
+
expect(filesManager.delete).toHaveBeenCalledWith(req.user, req.space, expect.objectContaining({
|
|
819
|
+
lockTokens: [
|
|
820
|
+
'opaquelocktoken:abc'
|
|
821
|
+
]
|
|
822
|
+
}));
|
|
823
|
+
});
|
|
616
824
|
});
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
825
|
+
describe('Error handling', ()=>{
|
|
826
|
+
it('should handle lock conflict', async ()=>{
|
|
827
|
+
const lockError = new _filelockerror.LockConflict({
|
|
828
|
+
dbFilePath: 'file.txt',
|
|
829
|
+
options: {
|
|
830
|
+
lockRoot: '/webdav/locked'
|
|
831
|
+
}
|
|
832
|
+
}, 'Lock conflict');
|
|
833
|
+
filesManager.delete.mockRejectedValue(lockError);
|
|
834
|
+
const req = createBaseRequest({
|
|
835
|
+
method: 'DELETE'
|
|
836
|
+
});
|
|
837
|
+
const res = createMockResponse();
|
|
838
|
+
await service.delete(req, res);
|
|
839
|
+
expect(res.statusCode).toBe(_common.HttpStatus.LOCKED);
|
|
840
|
+
});
|
|
841
|
+
it('should handle file errors', async ()=>{
|
|
842
|
+
const fileError = new _fileerror.FileError(404, 'File not found');
|
|
843
|
+
filesManager.delete.mockRejectedValue(fileError);
|
|
844
|
+
const req = createBaseRequest({
|
|
845
|
+
method: 'DELETE'
|
|
846
|
+
});
|
|
847
|
+
const res = createMockResponse();
|
|
848
|
+
await service.delete(req, res);
|
|
849
|
+
expect(res.statusCode).toBe(404);
|
|
850
|
+
expect(res.body).toBe('File not found');
|
|
851
|
+
});
|
|
852
|
+
it('should throw HttpException for unexpected errors', async ()=>{
|
|
853
|
+
const unexpectedError = new Error('Database error');
|
|
854
|
+
filesManager.delete.mockRejectedValue(unexpectedError);
|
|
855
|
+
const req = createBaseRequest({
|
|
856
|
+
method: 'DELETE'
|
|
857
|
+
});
|
|
858
|
+
const res = createMockResponse();
|
|
859
|
+
await expect(service.delete(req, res)).rejects.toThrow(_common.HttpException);
|
|
860
|
+
});
|
|
628
861
|
});
|
|
629
862
|
});
|
|
630
863
|
describe('proppatch', ()=>{
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
864
|
+
describe('Base cases', ()=>{
|
|
865
|
+
it('should return 404 when resource does not exist', async ()=>{
|
|
866
|
+
;
|
|
867
|
+
_files.isPathExists.mockResolvedValue(false);
|
|
868
|
+
const req = createBaseRequest({
|
|
869
|
+
method: 'PROPPATCH',
|
|
870
|
+
dav: {
|
|
871
|
+
...createBaseRequest().dav,
|
|
872
|
+
body: {
|
|
873
|
+
propertyupdate: {
|
|
874
|
+
set: {
|
|
875
|
+
prop: [
|
|
876
|
+
{
|
|
877
|
+
lastmodified: '2024-01-01'
|
|
878
|
+
}
|
|
879
|
+
]
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
641
883
|
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
884
|
+
});
|
|
885
|
+
const res = createMockResponse();
|
|
886
|
+
await service.proppatch(req, res);
|
|
887
|
+
expect(res.statusCode).toBe(_common.HttpStatus.NOT_FOUND);
|
|
888
|
+
expect(res.body).toBe(req.dav.url);
|
|
889
|
+
});
|
|
890
|
+
it('should return 400 for unknown action tag', async ()=>{
|
|
891
|
+
;
|
|
892
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
893
|
+
const req = createBaseRequest({
|
|
894
|
+
method: 'PROPPATCH',
|
|
895
|
+
dav: {
|
|
896
|
+
...createBaseRequest().dav,
|
|
897
|
+
body: {
|
|
898
|
+
propertyupdate: {
|
|
899
|
+
invalidaction: {}
|
|
900
|
+
}
|
|
659
901
|
}
|
|
660
902
|
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
903
|
+
});
|
|
904
|
+
const res = createMockResponse();
|
|
905
|
+
await service.proppatch(req, res);
|
|
906
|
+
expect(res.statusCode).toBe(_common.HttpStatus.BAD_REQUEST);
|
|
907
|
+
expect(res.body).toContain('Unknown tag');
|
|
908
|
+
});
|
|
909
|
+
it('should return 400 when missing prop tag', async ()=>{
|
|
910
|
+
;
|
|
911
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
912
|
+
const req = createBaseRequest({
|
|
913
|
+
method: 'PROPPATCH',
|
|
914
|
+
dav: {
|
|
915
|
+
...createBaseRequest().dav,
|
|
916
|
+
body: {
|
|
917
|
+
propertyupdate: {
|
|
918
|
+
set: {
|
|
919
|
+
notprop: {}
|
|
920
|
+
}
|
|
679
921
|
}
|
|
680
922
|
}
|
|
681
923
|
}
|
|
682
|
-
}
|
|
924
|
+
});
|
|
925
|
+
const res = createMockResponse();
|
|
926
|
+
await service.proppatch(req, res);
|
|
927
|
+
expect(res.statusCode).toBe(_common.HttpStatus.BAD_REQUEST);
|
|
928
|
+
expect(res.body).toContain('Unknown tag');
|
|
683
929
|
});
|
|
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
930
|
});
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
931
|
+
describe('SET action', ()=>{
|
|
932
|
+
it('should successfully modify lastmodified property', async ()=>{
|
|
933
|
+
;
|
|
934
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
935
|
+
filesLockManager.checkConflicts.mockResolvedValue(undefined);
|
|
936
|
+
filesManager.touch.mockResolvedValue(undefined);
|
|
937
|
+
const req = createBaseRequest({
|
|
938
|
+
method: 'PROPPATCH',
|
|
939
|
+
dav: {
|
|
940
|
+
...createBaseRequest().dav,
|
|
941
|
+
httpVersion: 'HTTP/1.1',
|
|
942
|
+
body: {
|
|
943
|
+
propertyupdate: {
|
|
944
|
+
set: {
|
|
945
|
+
prop: [
|
|
946
|
+
{
|
|
947
|
+
lastmodified: '2024-01-01'
|
|
948
|
+
}
|
|
949
|
+
]
|
|
950
|
+
}
|
|
706
951
|
}
|
|
707
952
|
}
|
|
708
953
|
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
['Win32CreationTime']: 'keep'
|
|
738
|
-
}
|
|
739
|
-
]
|
|
954
|
+
});
|
|
955
|
+
const res = createMockResponse();
|
|
956
|
+
await service.proppatch(req, res);
|
|
957
|
+
expect(filesManager.touch).toHaveBeenCalled();
|
|
958
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
959
|
+
expect(res.contentType).toContain('application/xml');
|
|
960
|
+
expect(typeof res.body).toBe('string');
|
|
961
|
+
expect(res.body).toContain('lastmodified');
|
|
962
|
+
expect(res.body).toContain('200');
|
|
963
|
+
});
|
|
964
|
+
it('should return 207 with 403 for unsupported properties', async ()=>{
|
|
965
|
+
;
|
|
966
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
967
|
+
filesLockManager.checkConflicts.mockResolvedValue(undefined);
|
|
968
|
+
const req = createBaseRequest({
|
|
969
|
+
method: 'PROPPATCH',
|
|
970
|
+
dav: {
|
|
971
|
+
...createBaseRequest().dav,
|
|
972
|
+
httpVersion: 'HTTP/1.1',
|
|
973
|
+
body: {
|
|
974
|
+
propertyupdate: {
|
|
975
|
+
set: {
|
|
976
|
+
prop: [
|
|
977
|
+
{
|
|
978
|
+
unsupportedProp: 'value'
|
|
979
|
+
}
|
|
980
|
+
]
|
|
981
|
+
}
|
|
740
982
|
}
|
|
741
983
|
}
|
|
742
984
|
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
985
|
+
});
|
|
986
|
+
const res = createMockResponse();
|
|
987
|
+
await service.proppatch(req, res);
|
|
988
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
989
|
+
expect(res.body).toContain('unsupportedProp');
|
|
990
|
+
expect(res.body).toContain('403');
|
|
991
|
+
});
|
|
992
|
+
it('should handle Win32 properties correctly', async ()=>{
|
|
993
|
+
;
|
|
994
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
995
|
+
filesLockManager.checkConflicts.mockResolvedValue(undefined);
|
|
996
|
+
const req = createBaseRequest({
|
|
997
|
+
method: 'PROPPATCH',
|
|
998
|
+
dav: {
|
|
999
|
+
...createBaseRequest().dav,
|
|
1000
|
+
httpVersion: 'HTTP/1.1',
|
|
1001
|
+
body: {
|
|
1002
|
+
propertyupdate: {
|
|
1003
|
+
set: {
|
|
1004
|
+
prop: [
|
|
1005
|
+
{
|
|
1006
|
+
Win32CreationTime: '2024-01-01'
|
|
1007
|
+
}
|
|
1008
|
+
]
|
|
1009
|
+
}
|
|
768
1010
|
}
|
|
769
1011
|
}
|
|
770
1012
|
}
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
}
|
|
798
|
-
},
|
|
799
|
-
{
|
|
800
|
-
prop: {
|
|
801
|
-
['Win32CreationTime']: 'ignore'
|
|
802
|
-
}
|
|
1013
|
+
});
|
|
1014
|
+
const res = createMockResponse();
|
|
1015
|
+
await service.proppatch(req, res);
|
|
1016
|
+
expect(filesManager.touch).not.toHaveBeenCalled();
|
|
1017
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
1018
|
+
expect(res.body).toContain('Win32CreationTime');
|
|
1019
|
+
expect(res.body).toContain('200');
|
|
1020
|
+
});
|
|
1021
|
+
it('should return 424 failed dependency when touch fails', async ()=>{
|
|
1022
|
+
;
|
|
1023
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
1024
|
+
filesLockManager.checkConflicts.mockResolvedValue(undefined);
|
|
1025
|
+
filesManager.touch.mockRejectedValue(new Error('Touch failed'));
|
|
1026
|
+
const req = createBaseRequest({
|
|
1027
|
+
method: 'PROPPATCH',
|
|
1028
|
+
dav: {
|
|
1029
|
+
...createBaseRequest().dav,
|
|
1030
|
+
httpVersion: 'HTTP/1.1',
|
|
1031
|
+
body: {
|
|
1032
|
+
propertyupdate: {
|
|
1033
|
+
set: {
|
|
1034
|
+
prop: [
|
|
1035
|
+
{
|
|
1036
|
+
lastmodified: '2024-01-01'
|
|
1037
|
+
}
|
|
1038
|
+
]
|
|
803
1039
|
}
|
|
804
|
-
|
|
1040
|
+
}
|
|
805
1041
|
}
|
|
806
1042
|
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1043
|
+
});
|
|
1044
|
+
const res = createMockResponse();
|
|
1045
|
+
await service.proppatch(req, res);
|
|
1046
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
1047
|
+
expect(res.body).toContain('424');
|
|
1048
|
+
});
|
|
1049
|
+
it('should mark supported props as 424 when unsupported prop fails', async ()=>{
|
|
1050
|
+
;
|
|
1051
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
1052
|
+
filesLockManager.checkConflicts.mockResolvedValue(undefined);
|
|
1053
|
+
const req = createBaseRequest({
|
|
1054
|
+
method: 'PROPPATCH',
|
|
1055
|
+
dav: {
|
|
1056
|
+
...createBaseRequest().dav,
|
|
1057
|
+
httpVersion: 'HTTP/1.1',
|
|
1058
|
+
body: {
|
|
1059
|
+
propertyupdate: {
|
|
1060
|
+
set: {
|
|
1061
|
+
prop: [
|
|
1062
|
+
{
|
|
1063
|
+
unsupportedProp: 'fail'
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
Win32CreationTime: 'ok'
|
|
1067
|
+
}
|
|
1068
|
+
]
|
|
832
1069
|
}
|
|
833
1070
|
}
|
|
834
1071
|
}
|
|
835
1072
|
}
|
|
836
|
-
}
|
|
1073
|
+
});
|
|
1074
|
+
const res = createMockResponse();
|
|
1075
|
+
await service.proppatch(req, res);
|
|
1076
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
1077
|
+
expect(res.body).toContain('unsupportedProp');
|
|
1078
|
+
expect(res.body).toContain('403');
|
|
1079
|
+
expect(res.body).toContain('Win32CreationTime');
|
|
1080
|
+
expect(res.body).toContain('424');
|
|
837
1081
|
});
|
|
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
1082
|
});
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1083
|
+
describe('REMOVE action', ()=>{
|
|
1084
|
+
it('should handle REMOVE action on supported property', async ()=>{
|
|
1085
|
+
;
|
|
1086
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
1087
|
+
filesLockManager.checkConflicts.mockResolvedValue(undefined);
|
|
1088
|
+
const req = createBaseRequest({
|
|
1089
|
+
method: 'PROPPATCH',
|
|
1090
|
+
dav: {
|
|
1091
|
+
...createBaseRequest().dav,
|
|
1092
|
+
httpVersion: 'HTTP/1.1',
|
|
1093
|
+
body: {
|
|
1094
|
+
propertyupdate: {
|
|
1095
|
+
remove: {
|
|
1096
|
+
prop: [
|
|
1097
|
+
{
|
|
1098
|
+
Win32CreationTime: ''
|
|
1099
|
+
}
|
|
1100
|
+
]
|
|
1101
|
+
}
|
|
863
1102
|
}
|
|
864
1103
|
}
|
|
865
1104
|
}
|
|
866
|
-
}
|
|
1105
|
+
});
|
|
1106
|
+
const res = createMockResponse();
|
|
1107
|
+
await service.proppatch(req, res);
|
|
1108
|
+
expect(filesManager.touch).not.toHaveBeenCalled();
|
|
1109
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
1110
|
+
expect(res.body).toContain('Win32CreationTime');
|
|
1111
|
+
expect(res.body).toContain('200');
|
|
867
1112
|
});
|
|
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
1113
|
});
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1114
|
+
describe('Data normalization', ()=>{
|
|
1115
|
+
it('should normalize array of propertyupdate items', async ()=>{
|
|
1116
|
+
;
|
|
1117
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
1118
|
+
filesLockManager.checkConflicts.mockResolvedValue(undefined);
|
|
1119
|
+
filesManager.touch.mockResolvedValue(undefined);
|
|
1120
|
+
const req = createBaseRequest({
|
|
1121
|
+
method: 'PROPPATCH',
|
|
1122
|
+
dav: {
|
|
1123
|
+
...createBaseRequest().dav,
|
|
1124
|
+
httpVersion: 'HTTP/1.1',
|
|
1125
|
+
body: {
|
|
1126
|
+
propertyupdate: {
|
|
1127
|
+
set: [
|
|
891
1128
|
{
|
|
892
|
-
|
|
1129
|
+
prop: {
|
|
1130
|
+
lastmodified: '2024-01-01'
|
|
1131
|
+
}
|
|
893
1132
|
},
|
|
894
1133
|
{
|
|
895
|
-
|
|
1134
|
+
prop: {
|
|
1135
|
+
Win32CreationTime: 'ok'
|
|
1136
|
+
}
|
|
896
1137
|
}
|
|
897
1138
|
]
|
|
898
1139
|
}
|
|
899
1140
|
}
|
|
900
1141
|
}
|
|
901
|
-
}
|
|
1142
|
+
});
|
|
1143
|
+
const res = createMockResponse();
|
|
1144
|
+
await service.proppatch(req, res);
|
|
1145
|
+
expect(filesManager.touch).toHaveBeenCalled();
|
|
1146
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
1147
|
+
expect(res.body).toContain('lastmodified');
|
|
1148
|
+
});
|
|
1149
|
+
it('should wrap single prop object into array', async ()=>{
|
|
1150
|
+
;
|
|
1151
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
1152
|
+
filesLockManager.checkConflicts.mockResolvedValue(undefined);
|
|
1153
|
+
filesManager.touch.mockResolvedValue(undefined);
|
|
1154
|
+
const req = createBaseRequest({
|
|
1155
|
+
method: 'PROPPATCH',
|
|
1156
|
+
dav: {
|
|
1157
|
+
...createBaseRequest().dav,
|
|
1158
|
+
httpVersion: 'HTTP/1.1',
|
|
1159
|
+
body: {
|
|
1160
|
+
propertyupdate: {
|
|
1161
|
+
set: {
|
|
1162
|
+
prop: {
|
|
1163
|
+
lastmodified: '2024-01-01'
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
const res = createMockResponse();
|
|
1171
|
+
await service.proppatch(req, res);
|
|
1172
|
+
expect(filesManager.touch).toHaveBeenCalled();
|
|
1173
|
+
expect(res.statusCode).toBe(_common.HttpStatus.MULTI_STATUS);
|
|
902
1174
|
});
|
|
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
1175
|
});
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
{
|
|
929
|
-
Win32CreationTime: 'keep'
|
|
930
|
-
}
|
|
931
|
-
]
|
|
1176
|
+
describe('Lock handling', ()=>{
|
|
1177
|
+
it('should check lock conflicts before applying changes', async ()=>{
|
|
1178
|
+
;
|
|
1179
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
1180
|
+
filesLockManager.checkConflicts.mockResolvedValue(undefined);
|
|
1181
|
+
const req = createBaseRequest({
|
|
1182
|
+
method: 'PROPPATCH',
|
|
1183
|
+
dav: {
|
|
1184
|
+
...createBaseRequest().dav,
|
|
1185
|
+
body: {
|
|
1186
|
+
propertyupdate: {
|
|
1187
|
+
set: {
|
|
1188
|
+
prop: [
|
|
1189
|
+
{
|
|
1190
|
+
Win32CreationTime: 'ok'
|
|
1191
|
+
}
|
|
1192
|
+
]
|
|
1193
|
+
}
|
|
932
1194
|
}
|
|
933
1195
|
}
|
|
934
1196
|
}
|
|
935
|
-
}
|
|
1197
|
+
});
|
|
1198
|
+
const res = createMockResponse();
|
|
1199
|
+
await service.proppatch(req, res);
|
|
1200
|
+
expect(filesLockManager.checkConflicts).toHaveBeenCalledWith(req.space.dbFile, req.dav.depth, expect.objectContaining({
|
|
1201
|
+
userId: req.user.id,
|
|
1202
|
+
lockTokens: expect.any(Array)
|
|
1203
|
+
}));
|
|
1204
|
+
});
|
|
1205
|
+
it('should handle lock conflict error', async ()=>{
|
|
1206
|
+
;
|
|
1207
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
1208
|
+
const lockError = new _filelockerror.LockConflict({
|
|
1209
|
+
dbFilePath: 'file.txt',
|
|
1210
|
+
options: {
|
|
1211
|
+
lockRoot: '/webdav/locked'
|
|
1212
|
+
}
|
|
1213
|
+
}, 'Lock conflict');
|
|
1214
|
+
filesLockManager.checkConflicts.mockRejectedValue(lockError);
|
|
1215
|
+
const req = createBaseRequest({
|
|
1216
|
+
method: 'PROPPATCH',
|
|
1217
|
+
dav: {
|
|
1218
|
+
...createBaseRequest().dav,
|
|
1219
|
+
body: {
|
|
1220
|
+
propertyupdate: {
|
|
1221
|
+
set: {
|
|
1222
|
+
prop: [
|
|
1223
|
+
{
|
|
1224
|
+
lastmodified: '2024-01-01'
|
|
1225
|
+
}
|
|
1226
|
+
]
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
const res = createMockResponse();
|
|
1233
|
+
await service.proppatch(req, res);
|
|
1234
|
+
expect(res.statusCode).toBe(_common.HttpStatus.LOCKED);
|
|
936
1235
|
});
|
|
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
1236
|
});
|
|
951
1237
|
});
|
|
952
1238
|
describe('mkcol', ()=>{
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1239
|
+
describe('Success cases', ()=>{
|
|
1240
|
+
it('should create directory and return 201', async ()=>{
|
|
1241
|
+
filesManager.mkDir.mockResolvedValue(undefined);
|
|
1242
|
+
const req = createBaseRequest({
|
|
1243
|
+
method: 'MKCOL'
|
|
1244
|
+
});
|
|
1245
|
+
const res = createMockResponse();
|
|
1246
|
+
await service.mkcol(req, res);
|
|
1247
|
+
expect(filesManager.mkDir).toHaveBeenCalledWith(req.user, req.space, false, expect.objectContaining({
|
|
1248
|
+
depth: req.dav.depth,
|
|
1249
|
+
lockTokens: expect.any(Array)
|
|
1250
|
+
}));
|
|
1251
|
+
expect(res.statusCode).toBe(_common.HttpStatus.CREATED);
|
|
1252
|
+
});
|
|
961
1253
|
});
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1254
|
+
describe('Error handling', ()=>{
|
|
1255
|
+
it('should handle lock conflict', async ()=>{
|
|
1256
|
+
const lockError = new _filelockerror.LockConflict({
|
|
1257
|
+
dbFilePath: 'dir',
|
|
1258
|
+
options: {
|
|
1259
|
+
lockRoot: '/webdav/locked'
|
|
1260
|
+
}
|
|
1261
|
+
}, 'Lock conflict');
|
|
1262
|
+
filesManager.mkDir.mockRejectedValue(lockError);
|
|
1263
|
+
const req = createBaseRequest({
|
|
1264
|
+
method: 'MKCOL'
|
|
1265
|
+
});
|
|
1266
|
+
const res = createMockResponse();
|
|
1267
|
+
await service.mkcol(req, res);
|
|
1268
|
+
expect(res.statusCode).toBe(_common.HttpStatus.LOCKED);
|
|
1269
|
+
});
|
|
1270
|
+
it('should handle file errors', async ()=>{
|
|
1271
|
+
const fileError = new _fileerror.FileError(409, 'Directory already exists');
|
|
1272
|
+
filesManager.mkDir.mockRejectedValue(fileError);
|
|
1273
|
+
const req = createBaseRequest({
|
|
1274
|
+
method: 'MKCOL'
|
|
1275
|
+
});
|
|
1276
|
+
const res = createMockResponse();
|
|
1277
|
+
await service.mkcol(req, res);
|
|
1278
|
+
expect(res.statusCode).toBe(409);
|
|
1279
|
+
expect(res.body).toBe('Directory already exists');
|
|
1280
|
+
});
|
|
1281
|
+
it('should throw HttpException for unexpected errors', async ()=>{
|
|
1282
|
+
const unexpectedError = new Error('Filesystem error');
|
|
1283
|
+
filesManager.mkDir.mockRejectedValue(unexpectedError);
|
|
1284
|
+
const req = createBaseRequest({
|
|
1285
|
+
method: 'MKCOL'
|
|
1286
|
+
});
|
|
1287
|
+
const res = createMockResponse();
|
|
1288
|
+
await expect(service.mkcol(req, res)).rejects.toThrow(_common.HttpException);
|
|
1289
|
+
});
|
|
972
1290
|
});
|
|
973
1291
|
});
|
|
974
1292
|
describe('copyMove', ()=>{
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1293
|
+
describe('Base cases', ()=>{
|
|
1294
|
+
it('should return 404 when destination space not found', async ()=>{
|
|
1295
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(null);
|
|
1296
|
+
const req = createBaseRequest({
|
|
1297
|
+
method: 'MOVE',
|
|
1298
|
+
dav: {
|
|
1299
|
+
...createBaseRequest().dav,
|
|
1300
|
+
copyMove: {
|
|
1301
|
+
destination: '/webdav/unknown',
|
|
1302
|
+
isMove: true,
|
|
1303
|
+
overwrite: false
|
|
1304
|
+
}
|
|
984
1305
|
}
|
|
985
|
-
}
|
|
1306
|
+
});
|
|
1307
|
+
const res = createMockResponse();
|
|
1308
|
+
await service.copyMove(req, res);
|
|
1309
|
+
expect(res.statusCode).toBe(_common.HttpStatus.NOT_FOUND);
|
|
1310
|
+
expect(res.body).toBe('/webdav/unknown');
|
|
986
1311
|
});
|
|
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
1312
|
});
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1313
|
+
describe('COPY operation', ()=>{
|
|
1314
|
+
it('should copy file and return 201 when destination does not exist', async ()=>{
|
|
1315
|
+
const dstSpace = {
|
|
1316
|
+
...createBaseRequest().space,
|
|
1317
|
+
url: '/webdav/test/copy.txt',
|
|
1318
|
+
realPath: '/real/path/to/copy.txt',
|
|
1319
|
+
dbFile: {
|
|
1320
|
+
path: 'copy.txt',
|
|
1321
|
+
spaceId: 1,
|
|
1322
|
+
inTrash: false
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(dstSpace);
|
|
1326
|
+
_files.isPathExists.mockResolvedValue(false);
|
|
1327
|
+
jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(true);
|
|
1328
|
+
filesManager.copyMove.mockResolvedValue(undefined);
|
|
1329
|
+
const req = createBaseRequest({
|
|
1330
|
+
method: 'COPY',
|
|
1331
|
+
dav: {
|
|
1332
|
+
...createBaseRequest().dav,
|
|
1333
|
+
copyMove: {
|
|
1334
|
+
destination: '/webdav/test/copy.txt',
|
|
1335
|
+
isMove: false,
|
|
1336
|
+
overwrite: false
|
|
1011
1337
|
}
|
|
1012
|
-
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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
|
-
}
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
const res = createMockResponse();
|
|
1341
|
+
await service.copyMove(req, res);
|
|
1342
|
+
expect(filesManager.copyMove).toHaveBeenCalledWith(req.user, req.space, dstSpace, false, false, false, expect.objectContaining({
|
|
1343
|
+
depth: req.dav.depth,
|
|
1344
|
+
lockTokens: expect.any(Array)
|
|
1345
|
+
}));
|
|
1346
|
+
expect(res.statusCode).toBe(_common.HttpStatus.CREATED);
|
|
1347
|
+
});
|
|
1348
|
+
it('should copy file and return 204 when destination exists', async ()=>{
|
|
1349
|
+
const dstSpace = {
|
|
1350
|
+
...createBaseRequest().space,
|
|
1351
|
+
url: '/webdav/test/existing.txt',
|
|
1352
|
+
realPath: '/real/path/to/existing.txt',
|
|
1353
|
+
dbFile: {
|
|
1354
|
+
path: 'existing.txt',
|
|
1355
|
+
spaceId: 1,
|
|
1356
|
+
inTrash: false
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(dstSpace);
|
|
1360
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
1361
|
+
jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(true);
|
|
1362
|
+
filesManager.copyMove.mockResolvedValue(undefined);
|
|
1363
|
+
const req = createBaseRequest({
|
|
1364
|
+
method: 'COPY',
|
|
1365
|
+
dav: {
|
|
1366
|
+
...createBaseRequest().dav,
|
|
1367
|
+
copyMove: {
|
|
1368
|
+
destination: '/webdav/test/existing.txt',
|
|
1369
|
+
isMove: false,
|
|
1370
|
+
overwrite: true
|
|
1060
1371
|
}
|
|
1061
|
-
|
|
1062
|
-
}
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
const res = createMockResponse();
|
|
1375
|
+
await service.copyMove(req, res);
|
|
1376
|
+
expect(res.statusCode).toBe(_common.HttpStatus.NO_CONTENT);
|
|
1063
1377
|
});
|
|
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
1378
|
});
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
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
|
|
1379
|
+
describe('MOVE operation', ()=>{
|
|
1380
|
+
it('should move file and return 201 when destination does not exist', async ()=>{
|
|
1381
|
+
const dstSpace = {
|
|
1382
|
+
...createBaseRequest().space,
|
|
1383
|
+
url: '/webdav/test/moved.txt',
|
|
1384
|
+
realPath: '/real/path/to/moved.txt',
|
|
1385
|
+
dbFile: {
|
|
1386
|
+
path: 'moved.txt',
|
|
1387
|
+
spaceId: 1,
|
|
1388
|
+
inTrash: false
|
|
1090
1389
|
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1390
|
+
};
|
|
1391
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(dstSpace);
|
|
1392
|
+
_files.isPathExists.mockResolvedValue(false);
|
|
1393
|
+
jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(true);
|
|
1394
|
+
filesManager.copyMove.mockResolvedValue(undefined);
|
|
1395
|
+
const req = createBaseRequest({
|
|
1396
|
+
method: 'MOVE',
|
|
1397
|
+
dav: {
|
|
1398
|
+
...createBaseRequest().dav,
|
|
1399
|
+
copyMove: {
|
|
1400
|
+
destination: '/webdav/test/moved.txt',
|
|
1401
|
+
isMove: true,
|
|
1402
|
+
overwrite: false
|
|
1403
|
+
}
|
|
1105
1404
|
}
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
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
|
|
1405
|
+
});
|
|
1406
|
+
const res = createMockResponse();
|
|
1407
|
+
await service.copyMove(req, res);
|
|
1408
|
+
expect(filesManager.copyMove).toHaveBeenCalledWith(req.user, req.space, dstSpace, true, false, false, expect.any(Object));
|
|
1409
|
+
expect(res.statusCode).toBe(_common.HttpStatus.CREATED);
|
|
1410
|
+
});
|
|
1411
|
+
it('should move file and return 204 when destination exists', async ()=>{
|
|
1412
|
+
const dstSpace = {
|
|
1413
|
+
...createBaseRequest().space,
|
|
1414
|
+
url: '/webdav/test/existing.txt',
|
|
1415
|
+
realPath: '/real/path/to/existing.txt',
|
|
1416
|
+
dbFile: {
|
|
1417
|
+
path: 'existing.txt',
|
|
1418
|
+
spaceId: 1,
|
|
1419
|
+
inTrash: false
|
|
1136
1420
|
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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'
|
|
1421
|
+
};
|
|
1422
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(dstSpace);
|
|
1423
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
1424
|
+
jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(true);
|
|
1425
|
+
filesManager.copyMove.mockResolvedValue(undefined);
|
|
1426
|
+
const req = createBaseRequest({
|
|
1427
|
+
method: 'MOVE',
|
|
1428
|
+
dav: {
|
|
1429
|
+
...createBaseRequest().dav,
|
|
1430
|
+
copyMove: {
|
|
1431
|
+
destination: '/webdav/test/existing.txt',
|
|
1432
|
+
isMove: true,
|
|
1433
|
+
overwrite: true
|
|
1170
1434
|
}
|
|
1171
|
-
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
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
|
-
}
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
const res = createMockResponse();
|
|
1438
|
+
await service.copyMove(req, res);
|
|
1439
|
+
expect(res.statusCode).toBe(_common.HttpStatus.NO_CONTENT);
|
|
1184
1440
|
});
|
|
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
1441
|
});
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1442
|
+
describe('If-Headers on destination', ()=>{
|
|
1443
|
+
it('should return early when evaluateIfHeaders fails for destination', async ()=>{
|
|
1444
|
+
const dstSpace = {
|
|
1445
|
+
...createBaseRequest().space,
|
|
1446
|
+
url: '/webdav/test/dest.txt',
|
|
1447
|
+
realPath: '/real/path/to/dest.txt',
|
|
1448
|
+
dbFile: {
|
|
1449
|
+
path: 'dest.txt',
|
|
1450
|
+
spaceId: 1,
|
|
1451
|
+
inTrash: false
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(dstSpace);
|
|
1455
|
+
jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(false);
|
|
1456
|
+
const req = createBaseRequest({
|
|
1457
|
+
method: 'COPY',
|
|
1458
|
+
dav: {
|
|
1459
|
+
...createBaseRequest().dav,
|
|
1460
|
+
copyMove: {
|
|
1461
|
+
destination: '/webdav/test/dest.txt',
|
|
1462
|
+
isMove: false,
|
|
1463
|
+
overwrite: true
|
|
1464
|
+
},
|
|
1465
|
+
ifHeaders: [
|
|
1466
|
+
{
|
|
1467
|
+
path: '/webdav/test/dest.txt',
|
|
1468
|
+
etag: {
|
|
1469
|
+
value: 'W/"wrong"',
|
|
1470
|
+
mustMatch: true
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
]
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
const res = createMockResponse();
|
|
1477
|
+
const result = await service.copyMove(req, res);
|
|
1478
|
+
expect(result).toBeUndefined();
|
|
1479
|
+
expect(filesManager.copyMove).not.toHaveBeenCalled();
|
|
1480
|
+
});
|
|
1481
|
+
it('should return 412 when destination If-Header haveLock mismatches', async ()=>{
|
|
1482
|
+
const dstSpace = {
|
|
1483
|
+
...createBaseRequest().space,
|
|
1484
|
+
url: '/webdav/test/dest.txt',
|
|
1485
|
+
realPath: '/real/path/to/dest.txt',
|
|
1486
|
+
dbFile: {
|
|
1487
|
+
path: 'dest.txt',
|
|
1488
|
+
spaceId: 1,
|
|
1489
|
+
inTrash: false
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(dstSpace);
|
|
1493
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
1494
|
+
_paths.dbFileFromSpace.mockReturnValue(dstSpace.dbFile);
|
|
1495
|
+
filesLockManager.getLocksByPath.mockResolvedValue([
|
|
1496
|
+
{
|
|
1497
|
+
key: 'lock1'
|
|
1498
|
+
}
|
|
1499
|
+
]);
|
|
1500
|
+
const req = createBaseRequest({
|
|
1501
|
+
method: 'COPY',
|
|
1502
|
+
dav: {
|
|
1503
|
+
...createBaseRequest().dav,
|
|
1504
|
+
copyMove: {
|
|
1505
|
+
destination: '/webdav/test/dest.txt',
|
|
1506
|
+
isMove: false,
|
|
1507
|
+
overwrite: true
|
|
1508
|
+
},
|
|
1509
|
+
ifHeaders: [
|
|
1510
|
+
{
|
|
1511
|
+
path: '/webdav/test/dest.txt',
|
|
1512
|
+
haveLock: {
|
|
1513
|
+
mustMatch: false
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
]
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
const res = createMockResponse();
|
|
1520
|
+
const result = await service.copyMove(req, res);
|
|
1521
|
+
expect(result).toBeUndefined();
|
|
1522
|
+
expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
|
|
1201
1523
|
});
|
|
1202
|
-
const res = makeRes();
|
|
1203
|
-
const ok = await service.evaluateIfHeaders(req, res);
|
|
1204
|
-
expect(ok).toBe(true);
|
|
1205
1524
|
});
|
|
1206
|
-
|
|
1207
|
-
{
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1525
|
+
describe('Error handling', ()=>{
|
|
1526
|
+
it('should handle lock conflict error', async ()=>{
|
|
1527
|
+
const dstSpace = {
|
|
1528
|
+
...createBaseRequest().space,
|
|
1529
|
+
url: '/webdav/test/dest.txt',
|
|
1530
|
+
realPath: '/real/path/to/dest.txt',
|
|
1531
|
+
dbFile: {
|
|
1532
|
+
path: 'dest.txt',
|
|
1533
|
+
spaceId: 1,
|
|
1534
|
+
inTrash: false
|
|
1535
|
+
}
|
|
1536
|
+
};
|
|
1537
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(dstSpace);
|
|
1538
|
+
jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(true);
|
|
1539
|
+
const lockError = new _filelockerror.LockConflict({
|
|
1540
|
+
dbFilePath: 'dest.txt',
|
|
1541
|
+
options: {
|
|
1542
|
+
lockRoot: '/webdav/locked'
|
|
1543
|
+
}
|
|
1544
|
+
}, 'Lock conflict');
|
|
1545
|
+
filesManager.copyMove.mockRejectedValue(lockError);
|
|
1546
|
+
const req = createBaseRequest({
|
|
1547
|
+
method: 'COPY',
|
|
1548
|
+
dav: {
|
|
1549
|
+
...createBaseRequest().dav,
|
|
1550
|
+
copyMove: {
|
|
1551
|
+
destination: '/webdav/test/dest.txt',
|
|
1552
|
+
isMove: false,
|
|
1553
|
+
overwrite: true
|
|
1215
1554
|
}
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
const res = createMockResponse();
|
|
1558
|
+
await service.copyMove(req, res);
|
|
1559
|
+
expect(res.statusCode).toBe(_common.HttpStatus.LOCKED);
|
|
1560
|
+
});
|
|
1561
|
+
it('should handle lock conflict without lockRoot (fallback to dbFilePath)', async ()=>{
|
|
1562
|
+
const dstSpace = {
|
|
1563
|
+
...createBaseRequest().space,
|
|
1564
|
+
url: '/webdav/test/dest.txt',
|
|
1565
|
+
realPath: '/real/path/to/dest.txt',
|
|
1566
|
+
dbFile: {
|
|
1567
|
+
path: 'dest.txt',
|
|
1223
1568
|
spaceId: 1,
|
|
1224
1569
|
inTrash: false
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(dstSpace);
|
|
1573
|
+
jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(true);
|
|
1574
|
+
const lockError = new _filelockerror.LockConflict({
|
|
1575
|
+
dbFilePath: 'dest.txt'
|
|
1576
|
+
}, 'Lock conflict');
|
|
1577
|
+
filesManager.copyMove.mockRejectedValue(lockError);
|
|
1578
|
+
const req = createBaseRequest({
|
|
1579
|
+
method: 'COPY',
|
|
1580
|
+
dav: {
|
|
1581
|
+
...createBaseRequest().dav,
|
|
1582
|
+
copyMove: {
|
|
1583
|
+
destination: '/webdav/test/dest.txt',
|
|
1584
|
+
isMove: false,
|
|
1585
|
+
overwrite: true
|
|
1237
1586
|
}
|
|
1238
|
-
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
const res = createMockResponse();
|
|
1590
|
+
await service.copyMove(req, res);
|
|
1591
|
+
expect(res.statusCode).toBe(_common.HttpStatus.LOCKED);
|
|
1592
|
+
});
|
|
1593
|
+
it('should handle file errors', async ()=>{
|
|
1594
|
+
const dstSpace = {
|
|
1595
|
+
...createBaseRequest().space,
|
|
1596
|
+
url: '/webdav/test/dest.txt',
|
|
1597
|
+
realPath: '/real/path/to/dest.txt',
|
|
1598
|
+
dbFile: {
|
|
1599
|
+
path: 'dest.txt',
|
|
1245
1600
|
spaceId: 1,
|
|
1246
1601
|
inTrash: false
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
{
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
}
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(dstSpace);
|
|
1605
|
+
jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(true);
|
|
1606
|
+
const fileError = new _fileerror.FileError(409, 'File conflict');
|
|
1607
|
+
filesManager.copyMove.mockRejectedValue(fileError);
|
|
1608
|
+
const req = createBaseRequest({
|
|
1609
|
+
method: 'MOVE',
|
|
1610
|
+
dav: {
|
|
1611
|
+
...createBaseRequest().dav,
|
|
1612
|
+
copyMove: {
|
|
1613
|
+
destination: '/webdav/test/dest.txt',
|
|
1614
|
+
isMove: true,
|
|
1615
|
+
overwrite: false
|
|
1262
1616
|
}
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
const res = createMockResponse();
|
|
1620
|
+
await service.copyMove(req, res);
|
|
1621
|
+
expect(res.statusCode).toBe(409);
|
|
1622
|
+
expect(res.body).toBe('File conflict');
|
|
1623
|
+
});
|
|
1624
|
+
it('should throw HttpException for unexpected errors', async ()=>{
|
|
1625
|
+
const dstSpace = {
|
|
1626
|
+
...createBaseRequest().space,
|
|
1627
|
+
url: '/webdav/test/dest.txt',
|
|
1628
|
+
realPath: '/real/path/to/dest.txt',
|
|
1629
|
+
dbFile: {
|
|
1630
|
+
path: 'dest.txt',
|
|
1631
|
+
spaceId: 1,
|
|
1632
|
+
inTrash: false
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1635
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(dstSpace);
|
|
1636
|
+
jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(true);
|
|
1637
|
+
const unexpectedError = new Error('Unexpected filesystem error');
|
|
1638
|
+
filesManager.copyMove.mockRejectedValue(unexpectedError);
|
|
1639
|
+
const req = createBaseRequest({
|
|
1640
|
+
method: 'COPY',
|
|
1641
|
+
dav: {
|
|
1642
|
+
...createBaseRequest().dav,
|
|
1643
|
+
copyMove: {
|
|
1644
|
+
destination: '/webdav/test/dest.txt',
|
|
1645
|
+
isMove: false,
|
|
1646
|
+
overwrite: true
|
|
1278
1647
|
}
|
|
1279
|
-
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
const res = createMockResponse();
|
|
1651
|
+
await expect(service.copyMove(req, res)).rejects.toThrow(_common.HttpException);
|
|
1652
|
+
});
|
|
1653
|
+
it('should include destination URL in error log', async ()=>{
|
|
1654
|
+
const dstSpace = {
|
|
1655
|
+
...createBaseRequest().space,
|
|
1656
|
+
url: '/webdav/test/dest.txt',
|
|
1657
|
+
realPath: '/real/path/to/dest.txt',
|
|
1658
|
+
dbFile: {
|
|
1659
|
+
path: 'dest.txt',
|
|
1660
|
+
spaceId: 1,
|
|
1661
|
+
inTrash: false
|
|
1662
|
+
}
|
|
1663
|
+
};
|
|
1664
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(dstSpace);
|
|
1665
|
+
jest.spyOn(service, 'evaluateIfHeaders').mockResolvedValue(true);
|
|
1666
|
+
const logSpy = jest.spyOn(service['logger'], 'error').mockImplementation(()=>undefined);
|
|
1667
|
+
filesManager.copyMove.mockRejectedValue(new Error('Copy failed'));
|
|
1668
|
+
const req = createBaseRequest({
|
|
1669
|
+
method: 'COPY',
|
|
1670
|
+
dav: {
|
|
1671
|
+
...createBaseRequest().dav,
|
|
1672
|
+
copyMove: {
|
|
1673
|
+
destination: '/webdav/test/dest.txt',
|
|
1674
|
+
isMove: false,
|
|
1675
|
+
overwrite: true
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
const res = createMockResponse();
|
|
1680
|
+
try {
|
|
1681
|
+
await service.copyMove(req, res);
|
|
1682
|
+
} catch {
|
|
1683
|
+
// Expected to throw
|
|
1293
1684
|
}
|
|
1685
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(' -> /webdav/test/dest.txt'));
|
|
1294
1686
|
});
|
|
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
1687
|
});
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1688
|
+
});
|
|
1689
|
+
describe('evaluateIfHeaders', ()=>{
|
|
1690
|
+
describe('Base cases', ()=>{
|
|
1691
|
+
it('should return true when no if-headers present', async ()=>{
|
|
1692
|
+
const req = createBaseRequest({
|
|
1693
|
+
dav: {
|
|
1694
|
+
...createBaseRequest().dav,
|
|
1695
|
+
ifHeaders: undefined
|
|
1696
|
+
}
|
|
1697
|
+
});
|
|
1698
|
+
const res = createMockResponse();
|
|
1699
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1700
|
+
expect(result).toBe(true);
|
|
1701
|
+
expect(res.statusCode).toBeUndefined();
|
|
1702
|
+
});
|
|
1703
|
+
it('should return true when at least one condition matches', async ()=>{
|
|
1704
|
+
;
|
|
1705
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
1706
|
+
const req = createBaseRequest({
|
|
1707
|
+
dav: {
|
|
1708
|
+
...createBaseRequest().dav,
|
|
1709
|
+
ifHeaders: [
|
|
1710
|
+
{
|
|
1711
|
+
etag: {
|
|
1712
|
+
value: 'W/"wrong-etag"',
|
|
1713
|
+
mustMatch: true
|
|
1714
|
+
}
|
|
1715
|
+
},
|
|
1716
|
+
{
|
|
1717
|
+
etag: {
|
|
1718
|
+
value: 'W/"etag-123"',
|
|
1719
|
+
mustMatch: true
|
|
1720
|
+
}
|
|
1311
1721
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1722
|
+
]
|
|
1723
|
+
}
|
|
1724
|
+
});
|
|
1725
|
+
const res = createMockResponse();
|
|
1726
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1727
|
+
expect(result).toBe(true);
|
|
1728
|
+
expect(res.statusCode).toBeUndefined();
|
|
1729
|
+
});
|
|
1730
|
+
it('should return false when path cannot be resolved', async ()=>{
|
|
1731
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(null);
|
|
1732
|
+
const req = createBaseRequest({
|
|
1733
|
+
dav: {
|
|
1734
|
+
...createBaseRequest().dav,
|
|
1735
|
+
ifHeaders: [
|
|
1736
|
+
{
|
|
1737
|
+
path: '/webdav/unknown/file.txt'
|
|
1317
1738
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
}
|
|
1739
|
+
]
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
const res = createMockResponse();
|
|
1743
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1744
|
+
expect(result).toBe(false);
|
|
1745
|
+
expect(res.statusCode).toBeUndefined();
|
|
1321
1746
|
});
|
|
1322
|
-
const res = makeRes();
|
|
1323
|
-
const ok = await service.evaluateIfHeaders(req, res);
|
|
1324
|
-
expect(ok).toBe(true);
|
|
1325
|
-
expect(res.statusCode).toBeUndefined();
|
|
1326
1747
|
});
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1748
|
+
describe('haveLock condition', ()=>{
|
|
1749
|
+
it('should return true when haveLock matches (lock exists, mustMatch=true)', async ()=>{
|
|
1750
|
+
;
|
|
1751
|
+
_paths.dbFileFromSpace.mockReturnValue({
|
|
1752
|
+
path: 'file.txt',
|
|
1753
|
+
spaceId: 1
|
|
1754
|
+
});
|
|
1755
|
+
filesLockManager.getLocksByPath.mockResolvedValue([
|
|
1756
|
+
{
|
|
1757
|
+
key: 'lock1'
|
|
1758
|
+
}
|
|
1759
|
+
]);
|
|
1760
|
+
const req = createBaseRequest({
|
|
1761
|
+
dav: {
|
|
1762
|
+
...createBaseRequest().dav,
|
|
1763
|
+
ifHeaders: [
|
|
1764
|
+
{
|
|
1765
|
+
haveLock: {
|
|
1766
|
+
mustMatch: true
|
|
1767
|
+
}
|
|
1337
1768
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
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
|
|
1769
|
+
]
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1772
|
+
const res = createMockResponse();
|
|
1773
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1774
|
+
expect(result).toBe(true);
|
|
1775
|
+
});
|
|
1776
|
+
it('should return true when haveLock matches (no lock, mustMatch=false)', async ()=>{
|
|
1777
|
+
;
|
|
1778
|
+
_paths.dbFileFromSpace.mockReturnValue({
|
|
1779
|
+
path: 'file.txt',
|
|
1780
|
+
spaceId: 1
|
|
1781
|
+
});
|
|
1782
|
+
filesLockManager.getLocksByPath.mockResolvedValue([]);
|
|
1783
|
+
const req = createBaseRequest({
|
|
1784
|
+
dav: {
|
|
1785
|
+
...createBaseRequest().dav,
|
|
1786
|
+
ifHeaders: [
|
|
1787
|
+
{
|
|
1788
|
+
haveLock: {
|
|
1789
|
+
mustMatch: false
|
|
1790
|
+
}
|
|
1371
1791
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
}
|
|
1792
|
+
]
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
const res = createMockResponse();
|
|
1796
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1797
|
+
expect(result).toBe(true);
|
|
1798
|
+
});
|
|
1799
|
+
it('should return false with 412 when haveLock mismatches (lock exists, mustMatch=false)', async ()=>{
|
|
1800
|
+
;
|
|
1801
|
+
_paths.dbFileFromSpace.mockReturnValue({
|
|
1802
|
+
path: 'file.txt',
|
|
1803
|
+
spaceId: 1
|
|
1804
|
+
});
|
|
1805
|
+
filesLockManager.getLocksByPath.mockResolvedValue([
|
|
1806
|
+
{
|
|
1807
|
+
key: 'lock1'
|
|
1808
|
+
}
|
|
1809
|
+
]);
|
|
1810
|
+
const req = createBaseRequest({
|
|
1811
|
+
dav: {
|
|
1812
|
+
...createBaseRequest().dav,
|
|
1813
|
+
ifHeaders: [
|
|
1814
|
+
{
|
|
1815
|
+
haveLock: {
|
|
1816
|
+
mustMatch: false
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
]
|
|
1820
|
+
}
|
|
1821
|
+
});
|
|
1822
|
+
const res = createMockResponse();
|
|
1823
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1824
|
+
expect(result).toBe(false);
|
|
1825
|
+
expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
|
|
1826
|
+
});
|
|
1827
|
+
it('should return false with 412 when haveLock mismatches (no lock, mustMatch=true)', async ()=>{
|
|
1828
|
+
;
|
|
1829
|
+
_paths.dbFileFromSpace.mockReturnValue({
|
|
1830
|
+
path: 'file.txt',
|
|
1831
|
+
spaceId: 1
|
|
1832
|
+
});
|
|
1833
|
+
filesLockManager.getLocksByPath.mockResolvedValue([]);
|
|
1834
|
+
const req = createBaseRequest({
|
|
1835
|
+
dav: {
|
|
1836
|
+
...createBaseRequest().dav,
|
|
1837
|
+
ifHeaders: [
|
|
1838
|
+
{
|
|
1839
|
+
haveLock: {
|
|
1840
|
+
mustMatch: true
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
]
|
|
1844
|
+
}
|
|
1845
|
+
});
|
|
1846
|
+
const res = createMockResponse();
|
|
1847
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1848
|
+
expect(result).toBe(false);
|
|
1849
|
+
expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
|
|
1850
|
+
});
|
|
1851
|
+
it('should return false with 412 when haveLock lookup throws error', async ()=>{
|
|
1852
|
+
;
|
|
1853
|
+
_paths.dbFileFromSpace.mockReturnValue({
|
|
1854
|
+
path: 'file.txt',
|
|
1855
|
+
spaceId: 1
|
|
1856
|
+
});
|
|
1857
|
+
filesLockManager.getLocksByPath.mockRejectedValue(new Error('Database error'));
|
|
1858
|
+
const req = createBaseRequest({
|
|
1859
|
+
dav: {
|
|
1860
|
+
...createBaseRequest().dav,
|
|
1861
|
+
ifHeaders: [
|
|
1862
|
+
{
|
|
1863
|
+
haveLock: {
|
|
1864
|
+
mustMatch: true
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
]
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
const res = createMockResponse();
|
|
1871
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1872
|
+
expect(result).toBe(false);
|
|
1873
|
+
expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
|
|
1874
|
+
expect(res.body).toBe('If header condition failed');
|
|
1375
1875
|
});
|
|
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
1876
|
});
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1877
|
+
describe('token condition', ()=>{
|
|
1878
|
+
it('should return true when token exists and path matches lockroot', async ()=>{
|
|
1879
|
+
filesLockManager.getLockByToken.mockResolvedValue({
|
|
1880
|
+
options: {
|
|
1881
|
+
lockRoot: '/webdav/test/file.txt'
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
const req = createBaseRequest({
|
|
1885
|
+
dav: {
|
|
1886
|
+
...createBaseRequest().dav,
|
|
1887
|
+
ifHeaders: [
|
|
1888
|
+
{
|
|
1889
|
+
token: {
|
|
1890
|
+
value: 'opaquelocktoken:abc',
|
|
1891
|
+
mustMatch: true
|
|
1892
|
+
}
|
|
1395
1893
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1894
|
+
]
|
|
1895
|
+
}
|
|
1896
|
+
});
|
|
1897
|
+
const res = createMockResponse();
|
|
1898
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1899
|
+
expect(result).toBe(true);
|
|
1900
|
+
});
|
|
1901
|
+
it('should return true when token exists and path is child of lockroot', async ()=>{
|
|
1902
|
+
filesLockManager.getLockByToken.mockResolvedValue({
|
|
1903
|
+
options: {
|
|
1904
|
+
lockRoot: '/webdav/test'
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
const req = createBaseRequest({
|
|
1908
|
+
dav: {
|
|
1909
|
+
...createBaseRequest().dav,
|
|
1910
|
+
url: '/webdav/test/subfolder/file.txt',
|
|
1911
|
+
ifHeaders: [
|
|
1912
|
+
{
|
|
1913
|
+
token: {
|
|
1914
|
+
value: 'opaquelocktoken:abc',
|
|
1915
|
+
mustMatch: true
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
]
|
|
1919
|
+
}
|
|
1920
|
+
});
|
|
1921
|
+
const res = createMockResponse();
|
|
1922
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1923
|
+
expect(result).toBe(true);
|
|
1924
|
+
});
|
|
1925
|
+
it('should return false with 412 when token not found', async ()=>{
|
|
1926
|
+
filesLockManager.getLockByToken.mockResolvedValue(null);
|
|
1927
|
+
const req = createBaseRequest({
|
|
1928
|
+
dav: {
|
|
1929
|
+
...createBaseRequest().dav,
|
|
1930
|
+
ifHeaders: [
|
|
1931
|
+
{
|
|
1932
|
+
token: {
|
|
1933
|
+
value: 'opaquelocktoken:missing',
|
|
1934
|
+
mustMatch: true
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
]
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
const res = createMockResponse();
|
|
1941
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1942
|
+
expect(result).toBe(false);
|
|
1943
|
+
expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
|
|
1944
|
+
});
|
|
1945
|
+
it('should return false with 412 when token exists but path does not match lockroot', async ()=>{
|
|
1946
|
+
filesLockManager.getLockByToken.mockResolvedValue({
|
|
1947
|
+
options: {
|
|
1948
|
+
lockRoot: '/webdav/other/file.txt'
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
const req = createBaseRequest({
|
|
1952
|
+
dav: {
|
|
1953
|
+
...createBaseRequest().dav,
|
|
1954
|
+
url: '/webdav/test/file.txt',
|
|
1955
|
+
ifHeaders: [
|
|
1956
|
+
{
|
|
1957
|
+
token: {
|
|
1958
|
+
value: 'opaquelocktoken:abc',
|
|
1959
|
+
mustMatch: true
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
]
|
|
1963
|
+
}
|
|
1964
|
+
});
|
|
1965
|
+
const res = createMockResponse();
|
|
1966
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
1967
|
+
expect(result).toBe(false);
|
|
1968
|
+
expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
|
|
1969
|
+
});
|
|
1970
|
+
it('should evaluate token condition with explicit path in if-header', async ()=>{
|
|
1971
|
+
const explicitSpace = {
|
|
1972
|
+
...createBaseRequest().space,
|
|
1973
|
+
url: '/webdav/explicit/path.txt',
|
|
1974
|
+
realPath: '/real/path/to/explicit.txt',
|
|
1975
|
+
dbFile: {
|
|
1976
|
+
path: 'explicit/path.txt',
|
|
1977
|
+
spaceId: 1,
|
|
1978
|
+
inTrash: false
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1981
|
+
webDAVSpaces.spaceEnv.mockResolvedValue(explicitSpace);
|
|
1982
|
+
filesLockManager.getLockByToken.mockResolvedValue({
|
|
1983
|
+
options: {
|
|
1984
|
+
lockRoot: '/webdav/explicit'
|
|
1985
|
+
}
|
|
1986
|
+
});
|
|
1987
|
+
const req = createBaseRequest({
|
|
1988
|
+
dav: {
|
|
1989
|
+
...createBaseRequest().dav,
|
|
1990
|
+
ifHeaders: [
|
|
1991
|
+
{
|
|
1992
|
+
path: '/webdav/explicit/path.txt',
|
|
1993
|
+
token: {
|
|
1994
|
+
value: 'opaquelocktoken:xyz',
|
|
1995
|
+
mustMatch: true
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
]
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
const res = createMockResponse();
|
|
2002
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
2003
|
+
expect(webDAVSpaces.spaceEnv).toHaveBeenCalledWith(req.user, '/webdav/explicit/path.txt');
|
|
2004
|
+
expect(result).toBe(true);
|
|
1407
2005
|
});
|
|
1408
|
-
const ok = await service.evaluateIfHeaders(req, res);
|
|
1409
|
-
expect(ok).toBe(true);
|
|
1410
|
-
expect(res.statusCode).toBeUndefined();
|
|
1411
2006
|
});
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
2007
|
+
describe('etag condition', ()=>{
|
|
2008
|
+
it('should return true when etag matches', async ()=>{
|
|
2009
|
+
;
|
|
2010
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
2011
|
+
_files.genEtag.mockReturnValue('W/"etag-123"');
|
|
2012
|
+
const req = createBaseRequest({
|
|
2013
|
+
dav: {
|
|
2014
|
+
...createBaseRequest().dav,
|
|
2015
|
+
ifHeaders: [
|
|
2016
|
+
{
|
|
2017
|
+
etag: {
|
|
2018
|
+
value: 'W/"etag-123"',
|
|
2019
|
+
mustMatch: true
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
]
|
|
2023
|
+
}
|
|
2024
|
+
});
|
|
2025
|
+
const res = createMockResponse();
|
|
2026
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
2027
|
+
expect(result).toBe(true);
|
|
2028
|
+
});
|
|
2029
|
+
it('should return true when etag does not match and mustMatch=false', async ()=>{
|
|
2030
|
+
;
|
|
2031
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
2032
|
+
_files.genEtag.mockReturnValue('W/"etag-123"');
|
|
2033
|
+
const req = createBaseRequest({
|
|
2034
|
+
dav: {
|
|
2035
|
+
...createBaseRequest().dav,
|
|
2036
|
+
ifHeaders: [
|
|
2037
|
+
{
|
|
2038
|
+
etag: {
|
|
2039
|
+
value: 'W/"different"',
|
|
2040
|
+
mustMatch: false
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
]
|
|
2044
|
+
}
|
|
2045
|
+
});
|
|
2046
|
+
const res = createMockResponse();
|
|
2047
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
2048
|
+
expect(result).toBe(true);
|
|
2049
|
+
});
|
|
2050
|
+
it('should return false with 412 when etag mismatches', async ()=>{
|
|
2051
|
+
;
|
|
2052
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
2053
|
+
_files.genEtag.mockReturnValue('W/"etag-123"');
|
|
2054
|
+
const req = createBaseRequest({
|
|
2055
|
+
dav: {
|
|
2056
|
+
...createBaseRequest().dav,
|
|
2057
|
+
ifHeaders: [
|
|
2058
|
+
{
|
|
2059
|
+
etag: {
|
|
2060
|
+
value: 'W/"wrong-etag"',
|
|
2061
|
+
mustMatch: true
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
]
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
const res = createMockResponse();
|
|
2068
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
2069
|
+
expect(result).toBe(false);
|
|
2070
|
+
expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
|
|
2071
|
+
});
|
|
2072
|
+
it('should return false with 412 when resource does not exist (null etag)', async ()=>{
|
|
2073
|
+
;
|
|
2074
|
+
_files.isPathExists.mockResolvedValue(false);
|
|
2075
|
+
const req = createBaseRequest({
|
|
2076
|
+
dav: {
|
|
2077
|
+
...createBaseRequest().dav,
|
|
2078
|
+
ifHeaders: [
|
|
2079
|
+
{
|
|
2080
|
+
etag: {
|
|
2081
|
+
value: 'W/"etag-123"',
|
|
2082
|
+
mustMatch: true
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
]
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2088
|
+
const res = createMockResponse();
|
|
2089
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
2090
|
+
expect(result).toBe(false);
|
|
2091
|
+
expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
|
|
2092
|
+
});
|
|
2093
|
+
it('should cache etag for multiple conditions on same path', async ()=>{
|
|
2094
|
+
;
|
|
2095
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
2096
|
+
_files.genEtag.mockReturnValue('W/"etag-123"');
|
|
2097
|
+
const req = createBaseRequest({
|
|
2098
|
+
dav: {
|
|
2099
|
+
...createBaseRequest().dav,
|
|
2100
|
+
ifHeaders: [
|
|
2101
|
+
{
|
|
2102
|
+
etag: {
|
|
2103
|
+
value: 'W/"wrong1"',
|
|
2104
|
+
mustMatch: true
|
|
2105
|
+
}
|
|
2106
|
+
},
|
|
2107
|
+
{
|
|
2108
|
+
etag: {
|
|
2109
|
+
value: 'W/"etag-123"',
|
|
2110
|
+
mustMatch: true
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
]
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
const res = createMockResponse();
|
|
2117
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
2118
|
+
expect(result).toBe(true);
|
|
2119
|
+
expect(_files.genEtag).toHaveBeenCalledTimes(1); // Cached
|
|
1422
2120
|
});
|
|
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
2121
|
});
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
2122
|
+
describe('Multiple conditions', ()=>{
|
|
2123
|
+
it('should evaluate multiple conditions and return true if any matches', async ()=>{
|
|
2124
|
+
;
|
|
2125
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
2126
|
+
_paths.dbFileFromSpace.mockReturnValue({
|
|
2127
|
+
path: 'file.txt',
|
|
2128
|
+
spaceId: 1
|
|
2129
|
+
});
|
|
2130
|
+
filesLockManager.getLocksByPath.mockResolvedValue([]);
|
|
2131
|
+
const req = createBaseRequest({
|
|
2132
|
+
dav: {
|
|
2133
|
+
...createBaseRequest().dav,
|
|
2134
|
+
ifHeaders: [
|
|
2135
|
+
{
|
|
2136
|
+
haveLock: {
|
|
2137
|
+
mustMatch: true
|
|
2138
|
+
}
|
|
2139
|
+
},
|
|
2140
|
+
{
|
|
2141
|
+
haveLock: {
|
|
2142
|
+
mustMatch: false
|
|
2143
|
+
}
|
|
2144
|
+
} // Will succeed (no lock)
|
|
2145
|
+
]
|
|
2146
|
+
}
|
|
2147
|
+
});
|
|
2148
|
+
const res = createMockResponse();
|
|
2149
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
2150
|
+
expect(result).toBe(true);
|
|
2151
|
+
});
|
|
2152
|
+
it('should return false with 412 when all conditions fail', async ()=>{
|
|
2153
|
+
;
|
|
2154
|
+
_files.isPathExists.mockResolvedValue(true);
|
|
2155
|
+
_files.genEtag.mockReturnValue('W/"etag-123"');
|
|
2156
|
+
filesLockManager.getLockByToken.mockResolvedValue(null);
|
|
2157
|
+
const req = createBaseRequest({
|
|
2158
|
+
dav: {
|
|
2159
|
+
...createBaseRequest().dav,
|
|
2160
|
+
ifHeaders: [
|
|
2161
|
+
{
|
|
2162
|
+
etag: {
|
|
2163
|
+
value: 'W/"wrong1"',
|
|
2164
|
+
mustMatch: true
|
|
2165
|
+
}
|
|
2166
|
+
},
|
|
2167
|
+
{
|
|
2168
|
+
etag: {
|
|
2169
|
+
value: 'W/"wrong2"',
|
|
2170
|
+
mustMatch: true
|
|
2171
|
+
}
|
|
2172
|
+
},
|
|
2173
|
+
{
|
|
2174
|
+
token: {
|
|
2175
|
+
value: 'opaquelocktoken:missing',
|
|
2176
|
+
mustMatch: true
|
|
2177
|
+
}
|
|
1442
2178
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
}
|
|
2179
|
+
]
|
|
2180
|
+
}
|
|
2181
|
+
});
|
|
2182
|
+
const res = createMockResponse();
|
|
2183
|
+
const result = await service.evaluateIfHeaders(req, res);
|
|
2184
|
+
expect(result).toBe(false);
|
|
2185
|
+
expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
|
|
2186
|
+
expect(res.body).toBe('If header condition failed');
|
|
1446
2187
|
});
|
|
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
2188
|
});
|
|
1452
2189
|
});
|
|
1453
|
-
describe('lockRefresh', ()=>{
|
|
1454
|
-
|
|
1455
|
-
{
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
|
2190
|
+
describe('lockRefresh (private method)', ()=>{
|
|
2191
|
+
describe('Parameter validation', ()=>{
|
|
2192
|
+
it('should return 400 when no if-headers present', async ()=>{
|
|
2193
|
+
const req = createBaseRequest({
|
|
2194
|
+
dav: {
|
|
2195
|
+
...createBaseRequest().dav,
|
|
2196
|
+
body: undefined,
|
|
2197
|
+
ifHeaders: []
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2200
|
+
const res = createMockResponse();
|
|
2201
|
+
await service.lockRefresh(req, res, 'file.txt');
|
|
2202
|
+
expect(res.statusCode).toBe(_common.HttpStatus.BAD_REQUEST);
|
|
2203
|
+
expect(res.body).toContain('Expected a lock token');
|
|
2204
|
+
});
|
|
2205
|
+
it('should return 400 when more than one if-header present', async ()=>{
|
|
2206
|
+
const req = createBaseRequest({
|
|
2207
|
+
dav: {
|
|
2208
|
+
...createBaseRequest().dav,
|
|
2209
|
+
body: undefined,
|
|
2210
|
+
ifHeaders: [
|
|
2211
|
+
{
|
|
2212
|
+
token: {
|
|
2213
|
+
value: 'token1',
|
|
2214
|
+
mustMatch: true
|
|
2215
|
+
}
|
|
2216
|
+
},
|
|
2217
|
+
{
|
|
2218
|
+
token: {
|
|
2219
|
+
value: 'token2',
|
|
2220
|
+
mustMatch: true
|
|
2221
|
+
}
|
|
1498
2222
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
}
|
|
2223
|
+
]
|
|
2224
|
+
}
|
|
2225
|
+
});
|
|
2226
|
+
const res = createMockResponse();
|
|
2227
|
+
await service.lockRefresh(req, res, 'file.txt');
|
|
2228
|
+
expect(res.statusCode).toBe(_common.HttpStatus.BAD_REQUEST);
|
|
2229
|
+
expect(res.body).toContain('Expected a lock token');
|
|
2230
|
+
});
|
|
2231
|
+
it('should return 400 when token extraction fails', async ()=>{
|
|
2232
|
+
const req = createBaseRequest({
|
|
2233
|
+
dav: {
|
|
2234
|
+
...createBaseRequest().dav,
|
|
2235
|
+
body: undefined,
|
|
2236
|
+
ifHeaders: [
|
|
2237
|
+
{
|
|
2238
|
+
notAToken: true
|
|
2239
|
+
}
|
|
2240
|
+
]
|
|
2241
|
+
}
|
|
2242
|
+
});
|
|
2243
|
+
const res = createMockResponse();
|
|
2244
|
+
jest.spyOn(_ifheader, 'extractOneToken').mockImplementation(()=>{
|
|
2245
|
+
throw new Error('No token found');
|
|
2246
|
+
});
|
|
2247
|
+
await service.lockRefresh(req, res, 'file.txt');
|
|
2248
|
+
expect(res.statusCode).toBe(_common.HttpStatus.BAD_REQUEST);
|
|
2249
|
+
expect(res.body).toContain('Unable to extract token');
|
|
1502
2250
|
});
|
|
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
2251
|
});
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
2252
|
+
describe('Token validation', ()=>{
|
|
2253
|
+
it('should return 412 when token not found or does not match path', async ()=>{
|
|
2254
|
+
const req = createBaseRequest({
|
|
2255
|
+
dav: {
|
|
2256
|
+
...createBaseRequest().dav,
|
|
2257
|
+
body: undefined,
|
|
2258
|
+
ifHeaders: [
|
|
2259
|
+
{
|
|
2260
|
+
token: {
|
|
2261
|
+
value: 'opaquelocktoken:missing',
|
|
2262
|
+
mustMatch: true
|
|
2263
|
+
}
|
|
1519
2264
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
2265
|
+
]
|
|
2266
|
+
}
|
|
2267
|
+
});
|
|
2268
|
+
const res = createMockResponse();
|
|
2269
|
+
jest.spyOn(_ifheader, 'extractOneToken').mockReturnValue('opaquelocktoken:missing');
|
|
2270
|
+
filesLockManager.isLockedWithToken.mockResolvedValue(null);
|
|
2271
|
+
await service.lockRefresh(req, res, 'file.txt');
|
|
2272
|
+
expect(filesLockManager.isLockedWithToken).toHaveBeenCalledWith('opaquelocktoken:missing', 'file.txt');
|
|
2273
|
+
expect(res.statusCode).toBe(_common.HttpStatus.PRECONDITION_FAILED);
|
|
2274
|
+
});
|
|
2275
|
+
it('should return 403 when lock owner is different user', async ()=>{
|
|
2276
|
+
const req = createBaseRequest({
|
|
2277
|
+
user: {
|
|
2278
|
+
id: 1,
|
|
2279
|
+
login: 'user1'
|
|
2280
|
+
},
|
|
2281
|
+
dav: {
|
|
2282
|
+
...createBaseRequest().dav,
|
|
2283
|
+
body: undefined,
|
|
2284
|
+
ifHeaders: [
|
|
2285
|
+
{
|
|
2286
|
+
token: {
|
|
2287
|
+
value: 'opaquelocktoken:abc',
|
|
2288
|
+
mustMatch: true
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
]
|
|
2292
|
+
}
|
|
2293
|
+
});
|
|
2294
|
+
const res = createMockResponse();
|
|
2295
|
+
jest.spyOn(_ifheader, 'extractOneToken').mockReturnValue('opaquelocktoken:abc');
|
|
2296
|
+
filesLockManager.isLockedWithToken.mockResolvedValue({
|
|
2297
|
+
owner: {
|
|
2298
|
+
id: 999,
|
|
2299
|
+
login: 'other-user'
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
await service.lockRefresh(req, res, 'file.txt');
|
|
2303
|
+
expect(res.statusCode).toBe(_common.HttpStatus.FORBIDDEN);
|
|
2304
|
+
expect(res.body).toBe('Lock token does not match owner');
|
|
1530
2305
|
});
|
|
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
2306
|
});
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
2307
|
+
describe('Successful refresh', ()=>{
|
|
2308
|
+
it('should refresh lock and return 200 with XML body', async ()=>{
|
|
2309
|
+
const req = createBaseRequest({
|
|
2310
|
+
dav: {
|
|
2311
|
+
...createBaseRequest().dav,
|
|
2312
|
+
body: undefined,
|
|
2313
|
+
lock: {
|
|
2314
|
+
...createBaseRequest().dav.lock,
|
|
2315
|
+
timeout: 180
|
|
2316
|
+
},
|
|
2317
|
+
ifHeaders: [
|
|
2318
|
+
{
|
|
2319
|
+
token: {
|
|
2320
|
+
value: 'opaquelocktoken:abc',
|
|
2321
|
+
mustMatch: true
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
]
|
|
2325
|
+
}
|
|
2326
|
+
});
|
|
2327
|
+
const res = createMockResponse();
|
|
2328
|
+
const mockLock = {
|
|
2329
|
+
owner: {
|
|
2330
|
+
id: 1,
|
|
2331
|
+
login: 'test-user'
|
|
1543
2332
|
},
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
2333
|
+
options: {
|
|
2334
|
+
lockRoot: '/webdav/test/file.txt',
|
|
2335
|
+
lockToken: 'opaquelocktoken:abc'
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
jest.spyOn(_ifheader, 'extractOneToken').mockReturnValue('opaquelocktoken:abc');
|
|
2339
|
+
filesLockManager.isLockedWithToken.mockResolvedValue(mockLock);
|
|
2340
|
+
await service.lockRefresh(req, res, 'file.txt');
|
|
2341
|
+
expect(filesLockManager.refreshLockTimeout).toHaveBeenCalledWith(mockLock, 180);
|
|
2342
|
+
expect(res.statusCode).toBe(_common.HttpStatus.OK);
|
|
2343
|
+
expect(res.contentType).toContain('application/xml');
|
|
2344
|
+
expect(typeof res.body).toBe('string');
|
|
2345
|
+
});
|
|
2346
|
+
it('should use default timeout when not specified', async ()=>{
|
|
2347
|
+
const req = createBaseRequest({
|
|
2348
|
+
dav: {
|
|
2349
|
+
...createBaseRequest().dav,
|
|
2350
|
+
body: undefined,
|
|
2351
|
+
lock: {
|
|
2352
|
+
...createBaseRequest().dav.lock,
|
|
2353
|
+
timeout: undefined
|
|
2354
|
+
},
|
|
2355
|
+
ifHeaders: [
|
|
2356
|
+
{
|
|
2357
|
+
token: {
|
|
2358
|
+
value: 'opaquelocktoken:abc',
|
|
2359
|
+
mustMatch: true
|
|
2360
|
+
}
|
|
1549
2361
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
}
|
|
2362
|
+
]
|
|
2363
|
+
}
|
|
2364
|
+
});
|
|
2365
|
+
const res = createMockResponse();
|
|
2366
|
+
const mockLock = {
|
|
2367
|
+
owner: {
|
|
2368
|
+
id: 1,
|
|
2369
|
+
login: 'test-user'
|
|
2370
|
+
},
|
|
2371
|
+
options: {
|
|
2372
|
+
lockRoot: '/webdav/test/file.txt'
|
|
2373
|
+
}
|
|
2374
|
+
};
|
|
2375
|
+
jest.spyOn(_ifheader, 'extractOneToken').mockReturnValue('opaquelocktoken:abc');
|
|
2376
|
+
filesLockManager.isLockedWithToken.mockResolvedValue(mockLock);
|
|
2377
|
+
await service.lockRefresh(req, res, 'file.txt');
|
|
2378
|
+
expect(filesLockManager.refreshLockTimeout).toHaveBeenCalledWith(mockLock, undefined);
|
|
1563
2379
|
});
|
|
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
2380
|
});
|
|
1570
2381
|
});
|
|
1571
|
-
describe('handleError
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
2382
|
+
describe('handleError (private method)', ()=>{
|
|
2383
|
+
describe('LockConflict errors', ()=>{
|
|
2384
|
+
it('should handle LockConflict with lockRoot', async ()=>{
|
|
2385
|
+
const lockError = new _filelockerror.LockConflict({
|
|
2386
|
+
dbFilePath: 'file.txt',
|
|
2387
|
+
options: {
|
|
2388
|
+
lockRoot: '/webdav/locked/resource'
|
|
2389
|
+
}
|
|
2390
|
+
}, 'Lock conflict');
|
|
2391
|
+
const req = createBaseRequest({
|
|
2392
|
+
method: 'PUT'
|
|
2393
|
+
});
|
|
2394
|
+
const res = createMockResponse();
|
|
2395
|
+
const result = service.handleError(req, res, lockError);
|
|
2396
|
+
expect(result).toBe(res);
|
|
2397
|
+
expect(res.statusCode).toBe(_common.HttpStatus.LOCKED);
|
|
2398
|
+
expect(res.contentType).toContain('application/xml');
|
|
2399
|
+
});
|
|
2400
|
+
it('should handle LockConflict without lockRoot (fallback to dbFilePath)', async ()=>{
|
|
2401
|
+
const lockError = new _filelockerror.LockConflict({
|
|
2402
|
+
dbFilePath: 'file.txt'
|
|
2403
|
+
}, 'Lock conflict');
|
|
2404
|
+
const req = createBaseRequest({
|
|
2405
|
+
method: 'DELETE'
|
|
2406
|
+
});
|
|
2407
|
+
const res = createMockResponse();
|
|
2408
|
+
const result = service.handleError(req, res, lockError);
|
|
2409
|
+
expect(result).toBe(res);
|
|
2410
|
+
expect(res.statusCode).toBe(_common.HttpStatus.LOCKED);
|
|
2411
|
+
});
|
|
1589
2412
|
});
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
2413
|
+
describe('FileError errors', ()=>{
|
|
2414
|
+
it('should handle FileError and return correct status code', async ()=>{
|
|
2415
|
+
const fileError = new _fileerror.FileError(409, 'Conflict: file already exists');
|
|
2416
|
+
const req = createBaseRequest({
|
|
2417
|
+
method: 'MKCOL'
|
|
2418
|
+
});
|
|
2419
|
+
const res = createMockResponse();
|
|
2420
|
+
const result = service.handleError(req, res, fileError);
|
|
2421
|
+
expect(result).toBe(res);
|
|
2422
|
+
expect(res.statusCode).toBe(409);
|
|
2423
|
+
expect(res.body).toBe('Conflict: file already exists');
|
|
2424
|
+
});
|
|
2425
|
+
it('should strip additional error information after comma', async ()=>{
|
|
2426
|
+
const fileError = new _fileerror.FileError(404, 'File not found, /real/path/details');
|
|
2427
|
+
const req = createBaseRequest({
|
|
2428
|
+
method: 'GET'
|
|
2429
|
+
});
|
|
2430
|
+
const res = createMockResponse();
|
|
2431
|
+
const result = service.handleError(req, res, fileError);
|
|
2432
|
+
expect(result).toBe(res);
|
|
2433
|
+
expect(res.statusCode).toBe(404);
|
|
2434
|
+
expect(res.body).toBe('File not found');
|
|
2435
|
+
});
|
|
1601
2436
|
});
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
2437
|
+
describe('Unexpected errors', ()=>{
|
|
2438
|
+
it('should throw HttpException for unexpected errors', ()=>{
|
|
2439
|
+
const unexpectedError = new Error('Database connection failed');
|
|
2440
|
+
const req = createBaseRequest({
|
|
2441
|
+
method: 'PUT'
|
|
2442
|
+
});
|
|
2443
|
+
const res = createMockResponse();
|
|
2444
|
+
expect(()=>{
|
|
2445
|
+
;
|
|
2446
|
+
service.handleError(req, res, unexpectedError);
|
|
2447
|
+
}).toThrow(_common.HttpException);
|
|
2448
|
+
});
|
|
2449
|
+
it('should log error with method and URL', ()=>{
|
|
2450
|
+
const logSpy = jest.spyOn(service['logger'], 'error').mockImplementation(()=>undefined);
|
|
2451
|
+
const req = createBaseRequest({
|
|
2452
|
+
method: 'PUT',
|
|
2453
|
+
dav: {
|
|
2454
|
+
...createBaseRequest().dav,
|
|
2455
|
+
url: '/webdav/test.txt'
|
|
2456
|
+
}
|
|
2457
|
+
});
|
|
2458
|
+
const res = createMockResponse();
|
|
2459
|
+
const error = new Error('Test error');
|
|
2460
|
+
try {
|
|
2461
|
+
;
|
|
2462
|
+
service.handleError(req, res, error);
|
|
2463
|
+
} catch {
|
|
2464
|
+
// Expected to throw
|
|
2465
|
+
}
|
|
2466
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('PUT'));
|
|
2467
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('/webdav/test.txt'));
|
|
2468
|
+
});
|
|
2469
|
+
it('should include destination URL in log when provided', ()=>{
|
|
2470
|
+
const logSpy = jest.spyOn(service['logger'], 'error').mockImplementation(()=>undefined);
|
|
2471
|
+
const req = createBaseRequest({
|
|
2472
|
+
method: 'COPY'
|
|
2473
|
+
});
|
|
2474
|
+
const res = createMockResponse();
|
|
2475
|
+
const error = new Error('Copy error');
|
|
2476
|
+
try {
|
|
2477
|
+
;
|
|
2478
|
+
service.handleError(req, res, error, '/webdav/destination.txt');
|
|
2479
|
+
} catch {
|
|
2480
|
+
// Expected to throw
|
|
2481
|
+
}
|
|
2482
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(' -> /webdav/destination.txt'));
|
|
1605
2483
|
});
|
|
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
2484
|
});
|
|
1617
2485
|
});
|
|
1618
2486
|
});
|