@sync-in/server 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +2 -1
  3. package/package.json +5 -5
  4. package/server/applications/comments/comments.controller.spec.js +103 -4
  5. package/server/applications/comments/comments.controller.spec.js.map +1 -1
  6. package/server/applications/comments/services/comments-manager.service.spec.js +409 -9
  7. package/server/applications/comments/services/comments-manager.service.spec.js.map +1 -1
  8. package/server/applications/files/adapters/files-indexer-mysql.service.spec.js +333 -0
  9. package/server/applications/files/adapters/files-indexer-mysql.service.spec.js.map +1 -0
  10. package/server/applications/files/constants/routes.js +6 -1
  11. package/server/applications/files/constants/routes.js.map +1 -1
  12. package/server/applications/files/files-only-office.controller.js +11 -0
  13. package/server/applications/files/files-only-office.controller.js.map +1 -1
  14. package/server/applications/files/files-only-office.controller.spec.js +97 -3
  15. package/server/applications/files/files-only-office.controller.spec.js.map +1 -1
  16. package/server/applications/files/files-tasks.controller.spec.js +91 -1
  17. package/server/applications/files/files-tasks.controller.spec.js.map +1 -1
  18. package/server/applications/files/files.controller.spec.js +268 -46
  19. package/server/applications/files/files.controller.spec.js.map +1 -1
  20. package/server/applications/files/guards/files-only-office.guard.spec.js +77 -1
  21. package/server/applications/files/guards/files-only-office.guard.spec.js.map +1 -1
  22. package/server/applications/files/services/files-only-office-manager.service.js +5 -0
  23. package/server/applications/files/services/files-only-office-manager.service.js.map +1 -1
  24. package/server/applications/links/links.controller.spec.js +91 -58
  25. package/server/applications/links/links.controller.spec.js.map +1 -1
  26. package/server/applications/links/services/links-manager.service.js +4 -6
  27. package/server/applications/links/services/links-manager.service.js.map +1 -1
  28. package/server/applications/links/services/links-manager.service.spec.js +378 -14
  29. package/server/applications/links/services/links-manager.service.spec.js.map +1 -1
  30. package/server/applications/links/services/links-queries.service.js +1 -1
  31. package/server/applications/links/services/links-queries.service.js.map +1 -1
  32. package/server/applications/notifications/notifications.controller.spec.js +56 -1
  33. package/server/applications/notifications/notifications.controller.spec.js.map +1 -1
  34. package/server/applications/notifications/services/notifications-manager.service.spec.js +461 -5
  35. package/server/applications/notifications/services/notifications-manager.service.spec.js.map +1 -1
  36. package/server/applications/shares/services/shares-manager.service.spec.js +590 -14
  37. package/server/applications/shares/services/shares-manager.service.spec.js.map +1 -1
  38. package/server/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.js +120 -0
  39. package/server/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.js.map +1 -0
  40. package/server/applications/sync/services/sync-clients-manager.service.spec.js +548 -8
  41. package/server/applications/sync/services/sync-clients-manager.service.spec.js.map +1 -1
  42. package/server/applications/sync/services/sync-manager.service.spec.js +837 -5
  43. package/server/applications/sync/services/sync-manager.service.spec.js.map +1 -1
  44. package/server/applications/sync/services/sync-paths-manager.service.spec.js +900 -7
  45. package/server/applications/sync/services/sync-paths-manager.service.spec.js.map +1 -1
  46. package/server/applications/users/services/admin-users-manager.service.js +22 -24
  47. package/server/applications/users/services/admin-users-manager.service.js.map +1 -1
  48. package/server/applications/users/services/admin-users-manager.service.spec.js +763 -17
  49. package/server/applications/users/services/admin-users-manager.service.spec.js.map +1 -1
  50. package/server/applications/users/services/users-manager.service.js +1 -1
  51. package/server/applications/users/services/users-manager.service.js.map +1 -1
  52. package/server/applications/users/services/users-manager.service.spec.js +938 -49
  53. package/server/applications/users/services/users-manager.service.spec.js.map +1 -1
  54. package/server/applications/webdav/decorators/if-header.decorator.js +4 -1
  55. package/server/applications/webdav/decorators/if-header.decorator.js.map +1 -1
  56. package/server/applications/webdav/filters/webdav.filter.spec.js +77 -0
  57. package/server/applications/webdav/filters/webdav.filter.spec.js.map +1 -0
  58. package/server/applications/webdav/guards/webdav-protocol.guard.js +3 -7
  59. package/server/applications/webdav/guards/webdav-protocol.guard.js.map +1 -1
  60. package/server/applications/webdav/guards/webdav-protocol.guard.spec.js +580 -0
  61. package/server/applications/webdav/guards/webdav-protocol.guard.spec.js.map +1 -0
  62. package/server/applications/webdav/services/webdav-methods.service.spec.js +1582 -3
  63. package/server/applications/webdav/services/webdav-methods.service.spec.js.map +1 -1
  64. package/server/applications/webdav/services/webdav-spaces.service.spec.js +390 -2
  65. package/server/applications/webdav/services/webdav-spaces.service.spec.js.map +1 -1
  66. package/server/applications/webdav/webdav.controller.js +2 -2
  67. package/server/applications/webdav/webdav.controller.js.map +1 -1
  68. package/server/authentication/guards/auth-basic.guard.js.map +1 -1
  69. package/server/authentication/guards/auth-digest.guard.js +1 -2
  70. package/server/authentication/guards/auth-digest.guard.js.map +1 -1
  71. package/server/authentication/guards/auth-local.guard.js.map +1 -1
  72. package/server/infrastructure/context/interceptors/context.interceptor.spec.js +135 -0
  73. package/server/infrastructure/context/interceptors/context.interceptor.spec.js.map +1 -0
  74. package/static/3rdpartylicenses.txt +26 -26
  75. package/static/assets/pdfjs/build/pdf.mjs +1177 -255
  76. package/static/assets/pdfjs/build/pdf.mjs.map +1 -1
  77. package/static/assets/pdfjs/build/pdf.sandbox.mjs +25 -2
  78. package/static/assets/pdfjs/build/pdf.sandbox.mjs.map +1 -1
  79. package/static/assets/pdfjs/build/pdf.worker.mjs +140 -16
  80. package/static/assets/pdfjs/build/pdf.worker.mjs.map +1 -1
  81. package/static/assets/pdfjs/version +1 -1
  82. package/static/assets/pdfjs/web/debugger.css +31 -0
  83. package/static/assets/pdfjs/web/debugger.mjs +144 -2
  84. package/static/assets/pdfjs/web/images/comment-editButton.svg +6 -1
  85. package/static/assets/pdfjs/web/locale/ach/viewer.ftl +0 -63
  86. package/static/assets/pdfjs/web/locale/af/viewer.ftl +0 -71
  87. package/static/assets/pdfjs/web/locale/an/viewer.ftl +0 -63
  88. package/static/assets/pdfjs/web/locale/ast/viewer.ftl +0 -60
  89. package/static/assets/pdfjs/web/locale/az/viewer.ftl +0 -63
  90. package/static/assets/pdfjs/web/locale/be/viewer.ftl +38 -0
  91. package/static/assets/pdfjs/web/locale/bg/viewer.ftl +0 -37
  92. package/static/assets/pdfjs/web/locale/bn/viewer.ftl +0 -63
  93. package/static/assets/pdfjs/web/locale/bo/viewer.ftl +0 -63
  94. package/static/assets/pdfjs/web/locale/br/viewer.ftl +0 -37
  95. package/static/assets/pdfjs/web/locale/brx/viewer.ftl +0 -63
  96. package/static/assets/pdfjs/web/locale/bs/viewer.ftl +22 -0
  97. package/static/assets/pdfjs/web/locale/ca/viewer.ftl +0 -54
  98. package/static/assets/pdfjs/web/locale/cak/viewer.ftl +0 -54
  99. package/static/assets/pdfjs/web/locale/ckb/viewer.ftl +0 -63
  100. package/static/assets/pdfjs/web/locale/cs/viewer.ftl +38 -0
  101. package/static/assets/pdfjs/web/locale/cy/viewer.ftl +38 -0
  102. package/static/assets/pdfjs/web/locale/da/viewer.ftl +38 -0
  103. package/static/assets/pdfjs/web/locale/de/viewer.ftl +38 -0
  104. package/static/assets/pdfjs/web/locale/dsb/viewer.ftl +38 -0
  105. package/static/assets/pdfjs/web/locale/el/viewer.ftl +38 -0
  106. package/static/assets/pdfjs/web/locale/en-CA/viewer.ftl +38 -0
  107. package/static/assets/pdfjs/web/locale/en-GB/viewer.ftl +38 -0
  108. package/static/assets/pdfjs/web/locale/en-US/viewer.ftl +25 -0
  109. package/static/assets/pdfjs/web/locale/eo/viewer.ftl +38 -0
  110. package/static/assets/pdfjs/web/locale/es-AR/viewer.ftl +38 -0
  111. package/static/assets/pdfjs/web/locale/es-CL/viewer.ftl +38 -0
  112. package/static/assets/pdfjs/web/locale/es-MX/viewer.ftl +0 -6
  113. package/static/assets/pdfjs/web/locale/et/viewer.ftl +0 -57
  114. package/static/assets/pdfjs/web/locale/fa/viewer.ftl +0 -37
  115. package/static/assets/pdfjs/web/locale/ff/viewer.ftl +0 -63
  116. package/static/assets/pdfjs/web/locale/fi/viewer.ftl +38 -0
  117. package/static/assets/pdfjs/web/locale/fr/viewer.ftl +38 -0
  118. package/static/assets/pdfjs/web/locale/fy-NL/viewer.ftl +38 -0
  119. package/static/assets/pdfjs/web/locale/ga-IE/viewer.ftl +0 -71
  120. package/static/assets/pdfjs/web/locale/gd/viewer.ftl +0 -54
  121. package/static/assets/pdfjs/web/locale/gl/viewer.ftl +8 -0
  122. package/static/assets/pdfjs/web/locale/gn/viewer.ftl +38 -0
  123. package/static/assets/pdfjs/web/locale/gu-IN/viewer.ftl +0 -63
  124. package/static/assets/pdfjs/web/locale/he/viewer.ftl +38 -0
  125. package/static/assets/pdfjs/web/locale/hi-IN/viewer.ftl +0 -60
  126. package/static/assets/pdfjs/web/locale/hsb/viewer.ftl +38 -0
  127. package/static/assets/pdfjs/web/locale/hu/viewer.ftl +38 -0
  128. package/static/assets/pdfjs/web/locale/hy-AM/viewer.ftl +0 -49
  129. package/static/assets/pdfjs/web/locale/hye/viewer.ftl +0 -60
  130. package/static/assets/pdfjs/web/locale/ia/viewer.ftl +38 -0
  131. package/static/assets/pdfjs/web/locale/is/viewer.ftl +0 -3
  132. package/static/assets/pdfjs/web/locale/it/viewer.ftl +31 -0
  133. package/static/assets/pdfjs/web/locale/ja/viewer.ftl +8 -0
  134. package/static/assets/pdfjs/web/locale/ka/viewer.ftl +48 -10
  135. package/static/assets/pdfjs/web/locale/kab/viewer.ftl +5 -0
  136. package/static/assets/pdfjs/web/locale/kk/viewer.ftl +8 -0
  137. package/static/assets/pdfjs/web/locale/km/viewer.ftl +0 -63
  138. package/static/assets/pdfjs/web/locale/kn/viewer.ftl +0 -71
  139. package/static/assets/pdfjs/web/locale/ko/viewer.ftl +38 -0
  140. package/static/assets/pdfjs/web/locale/lij/viewer.ftl +0 -63
  141. package/static/assets/pdfjs/web/locale/lo/viewer.ftl +0 -54
  142. package/static/assets/pdfjs/web/locale/lt/viewer.ftl +0 -60
  143. package/static/assets/pdfjs/web/locale/ltg/viewer.ftl +0 -63
  144. package/static/assets/pdfjs/web/locale/lv/viewer.ftl +0 -63
  145. package/static/assets/pdfjs/web/locale/meh/viewer.ftl +0 -75
  146. package/static/assets/pdfjs/web/locale/mk/viewer.ftl +0 -63
  147. package/static/assets/pdfjs/web/locale/ml/viewer.ftl +0 -3
  148. package/static/assets/pdfjs/web/locale/mr/viewer.ftl +0 -63
  149. package/static/assets/pdfjs/web/locale/ms/viewer.ftl +0 -63
  150. package/static/assets/pdfjs/web/locale/my/viewer.ftl +0 -71
  151. package/static/assets/pdfjs/web/locale/nb-NO/viewer.ftl +44 -6
  152. package/static/assets/pdfjs/web/locale/ne-NP/viewer.ftl +0 -71
  153. package/static/assets/pdfjs/web/locale/nl/viewer.ftl +38 -0
  154. package/static/assets/pdfjs/web/locale/nn-NO/viewer.ftl +45 -1
  155. package/static/assets/pdfjs/web/locale/oc/viewer.ftl +0 -31
  156. package/static/assets/pdfjs/web/locale/pa-IN/viewer.ftl +38 -0
  157. package/static/assets/pdfjs/web/locale/pl/viewer.ftl +39 -1
  158. package/static/assets/pdfjs/web/locale/pt-BR/viewer.ftl +38 -0
  159. package/static/assets/pdfjs/web/locale/ro/viewer.ftl +355 -1
  160. package/static/assets/pdfjs/web/locale/ru/viewer.ftl +38 -0
  161. package/static/assets/pdfjs/web/locale/sat/viewer.ftl +0 -54
  162. package/static/assets/pdfjs/web/locale/sc/viewer.ftl +0 -38
  163. package/static/assets/pdfjs/web/locale/scn/viewer.ftl +0 -92
  164. package/static/assets/pdfjs/web/locale/sco/viewer.ftl +0 -60
  165. package/static/assets/pdfjs/web/locale/si/viewer.ftl +0 -51
  166. package/static/assets/pdfjs/web/locale/sk/viewer.ftl +38 -0
  167. package/static/assets/pdfjs/web/locale/skr/viewer.ftl +0 -27
  168. package/static/assets/pdfjs/web/locale/sl/viewer.ftl +8 -0
  169. package/static/assets/pdfjs/web/locale/son/viewer.ftl +0 -71
  170. package/static/assets/pdfjs/web/locale/sr/viewer.ftl +0 -33
  171. package/static/assets/pdfjs/web/locale/sv-SE/viewer.ftl +38 -0
  172. package/static/assets/pdfjs/web/locale/szl/viewer.ftl +0 -63
  173. package/static/assets/pdfjs/web/locale/ta/viewer.ftl +0 -63
  174. package/static/assets/pdfjs/web/locale/te/viewer.ftl +0 -60
  175. package/static/assets/pdfjs/web/locale/tg/viewer.ftl +38 -0
  176. package/static/assets/pdfjs/web/locale/tl/viewer.ftl +0 -63
  177. package/static/assets/pdfjs/web/locale/tr/viewer.ftl +40 -2
  178. package/static/assets/pdfjs/web/locale/trs/viewer.ftl +0 -72
  179. package/static/assets/pdfjs/web/locale/ur/viewer.ftl +0 -60
  180. package/static/assets/pdfjs/web/locale/uz/viewer.ftl +0 -71
  181. package/static/assets/pdfjs/web/locale/vi/viewer.ftl +38 -0
  182. package/static/assets/pdfjs/web/locale/wo/viewer.ftl +0 -77
  183. package/static/assets/pdfjs/web/locale/xh/viewer.ftl +0 -71
  184. package/static/assets/pdfjs/web/locale/zh-CN/viewer.ftl +38 -0
  185. package/static/assets/pdfjs/web/locale/zh-TW/viewer.ftl +38 -0
  186. package/static/assets/pdfjs/web/viewer.css +649 -120
  187. package/static/assets/pdfjs/web/viewer.html +19 -0
  188. package/static/assets/pdfjs/web/viewer.mjs +489 -38
  189. package/static/assets/pdfjs/web/viewer.mjs.map +1 -1
  190. package/static/chunk-22EANI6R.js +1 -0
  191. package/static/{chunk-N2LYWNTC.js → chunk-2456KVFZ.js} +1 -1
  192. package/static/{chunk-YCINY2YI.js → chunk-2LVCLKCK.js} +1 -1
  193. package/static/{chunk-5YKWZT33.js → chunk-2V5S7DWD.js} +1 -1
  194. package/static/{chunk-3GC2BQZD.js → chunk-44YDXGNZ.js} +1 -1
  195. package/static/{chunk-W3QXNDI5.js → chunk-4LSJLWYV.js} +1 -1
  196. package/static/{chunk-T55FAU2O.js → chunk-4UT5VH7R.js} +1 -1
  197. package/static/{chunk-VMQMD36Z.js → chunk-5GOMMRRE.js} +1 -1
  198. package/static/{chunk-TXPODW5Q.js → chunk-5J4VRDKB.js} +1 -1
  199. package/static/{chunk-FQ4AFNGE.js → chunk-6PVKNZ7Q.js} +1 -1
  200. package/static/{chunk-XE5YHU5J.js → chunk-BIUNUYZ5.js} +1 -1
  201. package/static/{chunk-X43VWRFP.js → chunk-DFQKHCDR.js} +1 -1
  202. package/static/{chunk-TDQAEVZN.js → chunk-EE2TDTY4.js} +1 -1
  203. package/static/{chunk-MOVWEZ7J.js → chunk-ESNDJ5T6.js} +1 -1
  204. package/static/{chunk-TCFKH6K6.js → chunk-GDKKLLEU.js} +1 -1
  205. package/static/{chunk-VHYIXL7R.js → chunk-GSR2MCQG.js} +1 -1
  206. package/static/{chunk-LJIGRUEF.js → chunk-HR7KS5BR.js} +1 -1
  207. package/static/chunk-HW2H3ISM.js +559 -0
  208. package/static/{chunk-HZA7R43P.js → chunk-IMB3C547.js} +1 -1
  209. package/static/{chunk-B2Y2RNFP.js → chunk-J4ALHUDX.js} +1 -1
  210. package/static/{chunk-PF4K7MVG.js → chunk-KP6LSQTK.js} +1 -1
  211. package/static/{chunk-C23BPTJZ.js → chunk-LUZCOHFN.js} +1 -1
  212. package/static/{chunk-GBCYYDCI.js → chunk-MHSCCXVL.js} +1 -1
  213. package/static/{chunk-PY3BGNJN.js → chunk-OMRQYBXV.js} +1 -1
  214. package/static/chunk-P7CTJ5BG.js +27 -0
  215. package/static/{chunk-Y2CDUS4J.js → chunk-P7PX67IR.js} +4 -4
  216. package/static/{chunk-VMUOUCEI.js → chunk-PPO7DBVO.js} +1 -1
  217. package/static/{chunk-TTQ37MUV.js → chunk-RSS6GYNE.js} +1 -1
  218. package/static/{chunk-WWIC7UW3.js → chunk-SLGGINMR.js} +1 -1
  219. package/static/{chunk-KZQCFEPT.js → chunk-UHD5XD3G.js} +1 -1
  220. package/static/{chunk-TNW2CGK6.js → chunk-UPYYAJCJ.js} +1 -1
  221. package/static/{chunk-6F55D74O.js → chunk-VHYPQ3D4.js} +1 -1
  222. package/static/{chunk-DGVNNICG.js → chunk-YQSDS6BO.js} +1 -1
  223. package/static/{chunk-MTRNPGS4.js → chunk-ZC5NIT55.js} +1 -1
  224. package/static/index.html +1 -1
  225. package/static/main-FYD34UEC.js +7 -0
  226. package/static/chunk-RSNLYAN6.js +0 -560
  227. package/static/chunk-WHMS3PJ3.js +0 -24
  228. package/static/chunk-ZZ3LHYOY.js +0 -1
  229. package/static/main-7LDKYVXO.js +0 -10
@@ -6,35 +6,867 @@
6
6
  Object.defineProperty(exports, "__esModule", {
7
7
  value: true
8
8
  });
9
+ const _common = require("@nestjs/common");
9
10
  const _testing = require("@nestjs/testing");
11
+ const _promises = /*#__PURE__*/ _interop_require_default(require("node:fs/promises"));
12
+ const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
13
+ const _operations = require("../../files/constants/operations");
14
+ const _fileerror = require("../../files/models/file-error");
15
+ const _filelockerror = require("../../files/models/file-lock-error");
10
16
  const _filesmanagerservice = require("../../files/services/files-manager.service");
17
+ const _files = require("../../files/utils/files");
11
18
  const _spacesmanagerservice = require("../../spaces/services/spaces-manager.service");
19
+ const _sync = require("../constants/sync");
12
20
  const _syncmanagerservice = require("./sync-manager.service");
13
21
  const _syncqueriesservice = require("./sync-queries.service");
22
+ function _interop_require_default(obj) {
23
+ return obj && obj.__esModule ? obj : {
24
+ default: obj
25
+ };
26
+ }
27
+ // Mock fs/promises used internally by the service
28
+ jest.mock('fs/promises', ()=>({
29
+ __esModule: true,
30
+ default: {
31
+ stat: jest.fn(),
32
+ readdir: jest.fn()
33
+ }
34
+ }));
35
+ // Mock helper functions used in service
36
+ jest.mock('../../files/utils/files', ()=>({
37
+ __esModule: true,
38
+ checksumFile: jest.fn(),
39
+ isPathExists: jest.fn(),
40
+ isPathIsDir: jest.fn(),
41
+ removeFiles: jest.fn(),
42
+ touchFile: jest.fn(),
43
+ sanitizePath: jest.fn((p)=>p)
44
+ }));
45
+ // Mock regExpPathPattern to a simple, predictable behavior
46
+ jest.mock('../../../common/functions', ()=>({
47
+ __esModule: true,
48
+ regExpPathPattern: (base)=>new RegExp('^' + base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
49
+ }));
50
+ // Mock routes helper used by copyMove to bypass repo validation
51
+ jest.mock('../utils/routes', ()=>({
52
+ __esModule: true,
53
+ SYNC_PATH_TO_SPACE_SEGMENTS: jest.fn((dst)=>dst)
54
+ }));
55
+ // Mock heavy providers to avoid configuration side-effects on import
56
+ jest.mock('../../files/services/files-manager.service', ()=>({
57
+ __esModule: true,
58
+ FilesManager: class FilesManager {
59
+ }
60
+ }));
61
+ jest.mock('../../spaces/services/spaces-manager.service', ()=>({
62
+ __esModule: true,
63
+ SpacesManager: class SpacesManager {
64
+ }
65
+ }));
66
+ jest.mock('./sync-queries.service', ()=>({
67
+ __esModule: true,
68
+ SyncQueries: class SyncQueries {
69
+ }
70
+ }));
71
+ const fsPromises = _promises.default;
72
+ // small helper to collect async generators
73
+ const collect = async (iter)=>{
74
+ const out = [];
75
+ for await (const i of iter)out.push(i);
76
+ return out;
77
+ };
14
78
  describe(_syncmanagerservice.SyncManager.name, ()=>{
15
79
  let service;
80
+ let filesManager;
81
+ let spacesManager;
82
+ let syncQueries;
16
83
  beforeAll(async ()=>{
84
+ filesManager = {
85
+ sendFileFromSpace: jest.fn(),
86
+ saveStream: jest.fn(),
87
+ delete: jest.fn(),
88
+ touch: jest.fn(),
89
+ mkDir: jest.fn(),
90
+ mkFile: jest.fn(),
91
+ copyMove: jest.fn()
92
+ };
93
+ spacesManager = {
94
+ spaceEnv: jest.fn()
95
+ };
96
+ syncQueries = {
97
+ getPathSettings: jest.fn()
98
+ };
17
99
  const module = await _testing.Test.createTestingModule({
18
100
  providers: [
19
101
  _syncmanagerservice.SyncManager,
20
102
  {
21
103
  provide: _spacesmanagerservice.SpacesManager,
22
- useValue: {}
104
+ useValue: spacesManager
23
105
  },
24
106
  {
25
107
  provide: _filesmanagerservice.FilesManager,
26
- useValue: {}
108
+ useValue: filesManager
27
109
  },
28
110
  {
29
111
  provide: _syncqueriesservice.SyncQueries,
30
- useValue: {}
112
+ useValue: syncQueries
31
113
  }
32
114
  ]
33
115
  }).compile();
116
+ module.useLogger([
117
+ 'fatal'
118
+ ]);
34
119
  service = module.get(_syncmanagerservice.SyncManager);
35
120
  });
36
- it('should be defined', ()=>{
37
- expect(service).toBeDefined();
121
+ beforeEach(()=>jest.clearAllMocks());
122
+ const makeReq = (over)=>({
123
+ method: 'PUT',
124
+ user: {
125
+ id: 1,
126
+ clientId: 42
127
+ },
128
+ space: {
129
+ realPath: '/base/file.txt',
130
+ url: '/space/file.txt'
131
+ },
132
+ ...over
133
+ });
134
+ const makeReply = ()=>{
135
+ const raw = {
136
+ writeHead: jest.fn(),
137
+ write: jest.fn(),
138
+ end: jest.fn()
139
+ };
140
+ return {
141
+ raw,
142
+ status: jest.fn().mockReturnThis()
143
+ };
144
+ };
145
+ describe('download', ()=>{
146
+ it('should stream file successfully', async ()=>{
147
+ const req = makeReq();
148
+ const res = makeReply();
149
+ const checks = jest.fn().mockResolvedValue(undefined);
150
+ const stream = jest.fn().mockResolvedValue(new _common.StreamableFile(Buffer.from('abc')));
151
+ filesManager.sendFileFromSpace.mockReturnValue({
152
+ checks,
153
+ stream
154
+ });
155
+ const result = await service.download(req, res);
156
+ expect(filesManager.sendFileFromSpace).toHaveBeenCalledWith(req.space);
157
+ expect(checks).toHaveBeenCalled();
158
+ expect(stream).toHaveBeenCalledWith(req, res);
159
+ expect(result).toBeInstanceOf(_common.StreamableFile);
160
+ });
161
+ it.each([
162
+ [
163
+ 'LockConflict maps to 423',
164
+ new _filelockerror.LockConflict(null, 'locked'),
165
+ _common.HttpStatus.LOCKED
166
+ ],
167
+ [
168
+ 'FileError maps to its http code',
169
+ new _fileerror.FileError(_common.HttpStatus.BAD_REQUEST, 'bad'),
170
+ _common.HttpStatus.BAD_REQUEST
171
+ ],
172
+ [
173
+ 'generic Error maps to 500',
174
+ new Error('oops'),
175
+ _common.HttpStatus.INTERNAL_SERVER_ERROR
176
+ ]
177
+ ])('should map errors (%s)', async (_title, thrown, expectedStatus)=>{
178
+ const req = makeReq();
179
+ const res = makeReply();
180
+ const checks = jest.fn().mockRejectedValue(thrown);
181
+ const stream = jest.fn();
182
+ filesManager.sendFileFromSpace.mockReturnValue({
183
+ checks,
184
+ stream
185
+ });
186
+ await expect(service.download(req, res)).rejects.toMatchObject({
187
+ status: expectedStatus
188
+ });
189
+ });
190
+ });
191
+ describe('upload', ()=>{
192
+ it('should upload with checksum OK and return ino', async ()=>{
193
+ const req = makeReq({
194
+ space: {
195
+ realPath: '/tmp/up.bin',
196
+ url: '/space/up.bin'
197
+ }
198
+ });
199
+ const dto = {
200
+ checksum: 'abc',
201
+ size: 10,
202
+ mtime: 1710000000
203
+ };
204
+ filesManager.saveStream.mockResolvedValue('abc');
205
+ fsPromises.stat.mockResolvedValue({
206
+ size: 10,
207
+ ino: 123,
208
+ mtime: new Date(1710000000 * 1000)
209
+ });
210
+ _files.touchFile.mockResolvedValue(undefined);
211
+ const r = await service.upload(req, dto);
212
+ expect(filesManager.saveStream).toHaveBeenCalledWith(req.user, req.space, req, {
213
+ tmpPath: expect.any(String),
214
+ checksumAlg: _sync.SYNC_CHECKSUM_ALG
215
+ });
216
+ expect(_files.touchFile).toHaveBeenCalledWith('/tmp/up.bin', 1710000000);
217
+ expect(r).toEqual({
218
+ ino: 123
219
+ });
220
+ expect(_files.removeFiles).not.toHaveBeenCalled();
221
+ });
222
+ it('should reject when checksum mismatches and remove tmp', async ()=>{
223
+ const req = makeReq();
224
+ const dto = {
225
+ checksum: 'abc',
226
+ size: 10,
227
+ mtime: 1710000000
228
+ };
229
+ filesManager.saveStream.mockResolvedValue('bad');
230
+ fsPromises.stat.mockResolvedValue({
231
+ size: 10,
232
+ ino: 123,
233
+ mtime: new Date(1710000000 * 1000)
234
+ });
235
+ await expect(service.upload(req, dto)).rejects.toBeInstanceOf(_common.HttpException);
236
+ expect(_files.removeFiles).toHaveBeenCalledWith(expect.any(String));
237
+ });
238
+ it('should upload without checksum', async ()=>{
239
+ const req = makeReq({
240
+ space: {
241
+ realPath: '/tmp/up2.bin',
242
+ url: '/space/up2.bin'
243
+ }
244
+ });
245
+ const dto = {
246
+ size: 5,
247
+ mtime: 1710000100
248
+ };
249
+ filesManager.saveStream.mockResolvedValue(undefined);
250
+ fsPromises.stat.mockResolvedValue({
251
+ size: 5,
252
+ ino: 321,
253
+ mtime: new Date(1710000100 * 1000)
254
+ });
255
+ const r = await service.upload(req, dto);
256
+ expect(filesManager.saveStream).toHaveBeenCalledWith(req.user, req.space, req, {
257
+ tmpPath: expect.any(String)
258
+ });
259
+ expect(_files.touchFile).toHaveBeenCalledWith('/tmp/up2.bin', 1710000100);
260
+ expect(r).toEqual({
261
+ ino: 321
262
+ });
263
+ });
264
+ it('should reject when size mismatches and remove tmp', async ()=>{
265
+ const req = makeReq();
266
+ const dto = {
267
+ size: 10,
268
+ mtime: 1710000100
269
+ };
270
+ filesManager.saveStream.mockResolvedValue(undefined);
271
+ fsPromises.stat.mockResolvedValue({
272
+ size: 99,
273
+ ino: 321,
274
+ mtime: new Date(1710000100 * 1000)
275
+ });
276
+ await expect(service.upload(req, dto)).rejects.toBeInstanceOf(_common.HttpException);
277
+ expect(_files.removeFiles).toHaveBeenCalledWith(expect.any(String));
278
+ });
279
+ });
280
+ describe('delete', ()=>{
281
+ it('should delete successfully', async ()=>{
282
+ const req = makeReq();
283
+ filesManager.delete.mockResolvedValue(undefined);
284
+ await expect(service.delete(req)).resolves.toBeUndefined();
285
+ expect(filesManager.delete).toHaveBeenCalledWith(req.user, req.space);
286
+ });
287
+ it('should map errors via handleError', async ()=>{
288
+ const req = makeReq();
289
+ filesManager.delete.mockRejectedValue(new _filelockerror.LockConflict(null, 'locked'));
290
+ await expect(service.delete(req)).rejects.toMatchObject({
291
+ status: _common.HttpStatus.LOCKED
292
+ });
293
+ });
294
+ });
295
+ describe('props', ()=>{
296
+ it('should touch successfully', async ()=>{
297
+ const req = makeReq();
298
+ filesManager.touch.mockResolvedValue(undefined);
299
+ await expect(service.props(req, {
300
+ mtime: 1710000200
301
+ })).resolves.toBeUndefined();
302
+ expect(filesManager.touch).toHaveBeenCalledWith(req.user, req.space, 1710000200, false);
303
+ });
304
+ it('should map errors via handleError', async ()=>{
305
+ const req = makeReq();
306
+ filesManager.touch.mockRejectedValue(new _fileerror.FileError(_common.HttpStatus.BAD_REQUEST, 'bad'));
307
+ await expect(service.props(req, {
308
+ mtime: 123
309
+ })).rejects.toMatchObject({
310
+ status: _common.HttpStatus.BAD_REQUEST
311
+ });
312
+ });
313
+ });
314
+ describe('make', ()=>{
315
+ it('should create directory and return ino', async ()=>{
316
+ const req = makeReq({
317
+ space: {
318
+ realPath: '/tmp/newdir',
319
+ url: '/space/newdir'
320
+ }
321
+ });
322
+ filesManager.mkDir.mockResolvedValue(undefined);
323
+ fsPromises.stat.mockResolvedValue({
324
+ ino: 555
325
+ });
326
+ _files.touchFile.mockResolvedValue(undefined);
327
+ const r = await service.make(req, {
328
+ type: 'directory',
329
+ mtime: 1710000300
330
+ });
331
+ expect(filesManager.mkDir).toHaveBeenCalledWith(req.user, req.space, true);
332
+ expect(_files.touchFile).toHaveBeenCalledWith('/tmp/newdir', 1710000300);
333
+ expect(r).toEqual({
334
+ ino: 555
335
+ });
336
+ });
337
+ it('should create file and return ino', async ()=>{
338
+ const req = makeReq({
339
+ space: {
340
+ realPath: '/tmp/newfile',
341
+ url: '/space/newfile'
342
+ }
343
+ });
344
+ filesManager.mkFile.mockResolvedValue(undefined);
345
+ fsPromises.stat.mockResolvedValue({
346
+ ino: 777
347
+ });
348
+ const r = await service.make(req, {
349
+ type: 'file',
350
+ mtime: 1710000400
351
+ });
352
+ expect(filesManager.mkFile).toHaveBeenCalledWith(req.user, req.space, true);
353
+ expect(_files.touchFile).toHaveBeenCalledWith('/tmp/newfile', 1710000400);
354
+ expect(r).toEqual({
355
+ ino: 777
356
+ });
357
+ });
358
+ it('should map errors via handleError', async ()=>{
359
+ const req = makeReq();
360
+ filesManager.mkDir.mockRejectedValue(new _filelockerror.LockConflict(null, 'locked'));
361
+ await expect(service.make(req, {
362
+ type: 'directory',
363
+ mtime: 0
364
+ })).rejects.toMatchObject({
365
+ status: _common.HttpStatus.LOCKED
366
+ });
367
+ });
368
+ });
369
+ describe('copyMove', ()=>{
370
+ it('should move (no return) and not touch mtime', async ()=>{
371
+ const req = makeReq();
372
+ const dstSpace = {
373
+ realPath: '/dst/moved',
374
+ url: '/space/dst/moved'
375
+ };
376
+ spacesManager.spaceEnv.mockResolvedValue(dstSpace);
377
+ filesManager.copyMove.mockResolvedValue(undefined);
378
+ const r = await service.copyMove(req, {
379
+ destination: '/dst/moved'
380
+ }, true);
381
+ expect(spacesManager.spaceEnv).toHaveBeenCalled();
382
+ expect(filesManager.copyMove).toHaveBeenCalledWith(req.user, req.space, dstSpace, true, true, true);
383
+ expect(_files.touchFile).not.toHaveBeenCalled();
384
+ expect(r).toBeUndefined();
385
+ });
386
+ it('should copy, touch mtime when provided, and return ino/mtime', async ()=>{
387
+ const req = makeReq();
388
+ const dstSpace = {
389
+ realPath: '/dst/copied',
390
+ url: '/space/dst/copied'
391
+ };
392
+ spacesManager.spaceEnv.mockResolvedValue(dstSpace);
393
+ filesManager.copyMove.mockResolvedValue(undefined);
394
+ fsPromises.stat.mockResolvedValue({
395
+ ino: 999,
396
+ mtime: new Date(1710000500 * 1000)
397
+ });
398
+ const r = await service.copyMove(req, {
399
+ destination: '/dst/copied',
400
+ mtime: 1710000500
401
+ }, false);
402
+ expect(filesManager.copyMove).toHaveBeenCalledWith(req.user, req.space, dstSpace, false, true, true);
403
+ expect(_files.touchFile).toHaveBeenCalledWith('/dst/copied', 1710000500);
404
+ expect(r).toEqual({
405
+ ino: 999,
406
+ mtime: 1710000500
407
+ });
408
+ });
409
+ it('should copy without mtime and still return ino/mtime', async ()=>{
410
+ const req = makeReq();
411
+ const dstSpace = {
412
+ realPath: '/dst/copied2',
413
+ url: '/space/dst/copied2'
414
+ };
415
+ spacesManager.spaceEnv.mockResolvedValue(dstSpace);
416
+ filesManager.copyMove.mockResolvedValue(undefined);
417
+ fsPromises.stat.mockResolvedValue({
418
+ ino: 1001,
419
+ mtime: new Date(1710000600 * 1000)
420
+ });
421
+ const r = await service.copyMove(req, {
422
+ destination: '/dst/copied2'
423
+ }, false);
424
+ expect(_files.touchFile).not.toHaveBeenCalled();
425
+ expect(r).toEqual({
426
+ ino: 1001,
427
+ mtime: 1710000600
428
+ });
429
+ });
430
+ it('should map errors via handleError', async ()=>{
431
+ const req = makeReq();
432
+ const dstSpace = {
433
+ realPath: '/dst/err',
434
+ url: '/space/dst/err'
435
+ };
436
+ spacesManager.spaceEnv.mockResolvedValue(dstSpace);
437
+ filesManager.copyMove.mockRejectedValue(new _fileerror.FileError(_common.HttpStatus.BAD_REQUEST, 'bad'));
438
+ await expect(service.copyMove(req, {
439
+ destination: '/dst/err'
440
+ }, false)).rejects.toMatchObject({
441
+ status: _common.HttpStatus.BAD_REQUEST
442
+ });
443
+ });
444
+ it('should map errors via handleError for move (isMove=true)', async ()=>{
445
+ const req = makeReq();
446
+ const dstSpace = {
447
+ realPath: '/dst/err-move',
448
+ url: '/space/dst/err-move'
449
+ };
450
+ spacesManager.spaceEnv.mockResolvedValue(dstSpace);
451
+ filesManager.copyMove.mockRejectedValue(new _filelockerror.LockConflict(null, 'locked'));
452
+ const spy = jest.spyOn(service, 'handleError');
453
+ await expect(service.copyMove(req, {
454
+ destination: '/dst/err-move'
455
+ }, true)).rejects.toMatchObject({
456
+ status: _common.HttpStatus.LOCKED
457
+ });
458
+ expect(spy).toHaveBeenCalledWith(req.space, _operations.FILE_OPERATION.MOVE, expect.anything(), dstSpace);
459
+ });
460
+ });
461
+ describe('parseSyncPath', ()=>{
462
+ it('should delegate to parseFiles and yield file stats from the base directory', async ()=>{
463
+ const base = '/base-sync';
464
+ const space = {
465
+ realPath: base,
466
+ url: '/space-sync',
467
+ quotaIsExceeded: false
468
+ };
469
+ const dirent = {
470
+ name: 'afile',
471
+ parentPath: base,
472
+ isDirectory: ()=>false,
473
+ isFile: ()=>true
474
+ };
475
+ fsPromises.readdir.mockResolvedValue([
476
+ dirent
477
+ ]);
478
+ fsPromises.stat.mockResolvedValue({
479
+ isDirectory: ()=>false,
480
+ isFile: ()=>true,
481
+ size: 42,
482
+ ino: 7,
483
+ mtime: new Date(1234 * 1000)
484
+ });
485
+ const syncDiff = {
486
+ defaultFilters: new Set(),
487
+ secureDiff: false,
488
+ firstSync: true,
489
+ snapshot: new Map()
490
+ };
491
+ const out = await collect(service.parseSyncPath(space, syncDiff));
492
+ expect(out.length).toBe(1);
493
+ expect(out[0]).toHaveProperty('/afile');
494
+ const stats = out[0]['/afile'];
495
+ expect(Array.isArray(stats)).toBe(true);
496
+ expect(stats[_sync.F_STAT.IS_DIR]).toBe(false);
497
+ expect(stats[_sync.F_STAT.SIZE]).toBe(42);
498
+ expect(stats[_sync.F_STAT.MTIME]).toBe(1234);
499
+ expect(stats[_sync.F_STAT.INO]).toBe(7);
500
+ expect(stats[_sync.F_STAT.CHECKSUM]).toBeNull();
501
+ });
502
+ });
503
+ describe('diff', ()=>{
504
+ it('should fail when clientId is missing', async ()=>{
505
+ const res = makeReply();
506
+ await expect(service.diff({
507
+ id: 1
508
+ }, 1, {}, res)).rejects.toMatchObject({
509
+ status: _common.HttpStatus.BAD_REQUEST
510
+ });
511
+ });
512
+ it('should fail when path settings not found', async ()=>{
513
+ const res = makeReply();
514
+ const user = {
515
+ id: 1,
516
+ clientId: 9
517
+ };
518
+ syncQueries.getPathSettings.mockResolvedValue(undefined);
519
+ await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({
520
+ status: _common.HttpStatus.NOT_FOUND
521
+ });
522
+ });
523
+ it('should map spaceEnv thrown error to BAD_REQUEST', async ()=>{
524
+ const res = makeReply();
525
+ const user = {
526
+ id: 1,
527
+ clientId: 9
528
+ };
529
+ syncQueries.getPathSettings.mockResolvedValue({
530
+ remotePath: '/base'
531
+ });
532
+ spacesManager.spaceEnv.mockRejectedValue(new Error('boom'));
533
+ await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({
534
+ status: _common.HttpStatus.BAD_REQUEST,
535
+ message: 'boom'
536
+ });
537
+ });
538
+ it('should fail when space not found', async ()=>{
539
+ const res = makeReply();
540
+ const user = {
541
+ id: 1,
542
+ clientId: 9
543
+ };
544
+ syncQueries.getPathSettings.mockResolvedValue({
545
+ remotePath: '/base'
546
+ });
547
+ spacesManager.spaceEnv.mockResolvedValue(undefined);
548
+ await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({
549
+ status: _common.HttpStatus.NOT_FOUND
550
+ });
551
+ });
552
+ it('should fail when space quota exceeded', async ()=>{
553
+ const res = makeReply();
554
+ const user = {
555
+ id: 1,
556
+ clientId: 9
557
+ };
558
+ syncQueries.getPathSettings.mockResolvedValue({
559
+ remotePath: '/base'
560
+ });
561
+ spacesManager.spaceEnv.mockResolvedValue({
562
+ realPath: '/base',
563
+ url: '/space',
564
+ quotaIsExceeded: true
565
+ });
566
+ await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({
567
+ status: _common.HttpStatus.INSUFFICIENT_STORAGE
568
+ });
569
+ });
570
+ it('should fail when remote path does not exist', async ()=>{
571
+ const res = makeReply();
572
+ const user = {
573
+ id: 1,
574
+ clientId: 9
575
+ };
576
+ syncQueries.getPathSettings.mockResolvedValue({
577
+ remotePath: '/base'
578
+ });
579
+ spacesManager.spaceEnv.mockResolvedValue({
580
+ realPath: '/base',
581
+ url: '/space',
582
+ quotaIsExceeded: false
583
+ });
584
+ _files.isPathExists.mockResolvedValue(false);
585
+ await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({
586
+ status: _common.HttpStatus.NOT_FOUND,
587
+ message: 'Remote path not found : /base'
588
+ });
589
+ });
590
+ it('should fail when remote path is not a directory', async ()=>{
591
+ const res = makeReply();
592
+ const user = {
593
+ id: 1,
594
+ clientId: 9
595
+ };
596
+ syncQueries.getPathSettings.mockResolvedValue({
597
+ remotePath: '/base'
598
+ });
599
+ spacesManager.spaceEnv.mockResolvedValue({
600
+ realPath: '/base',
601
+ url: '/space',
602
+ quotaIsExceeded: false
603
+ });
604
+ _files.isPathExists.mockResolvedValue(true);
605
+ _files.isPathIsDir.mockResolvedValue(false);
606
+ await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({
607
+ status: _common.HttpStatus.BAD_REQUEST,
608
+ message: 'Remote path must be a directory'
609
+ });
610
+ });
611
+ it('should stream diff results successfully', async ()=>{
612
+ const res = makeReply();
613
+ const user = {
614
+ id: 1,
615
+ clientId: 9
616
+ };
617
+ syncQueries.getPathSettings.mockResolvedValue({
618
+ remotePath: '/base'
619
+ });
620
+ const space = {
621
+ realPath: '/base',
622
+ url: '/space',
623
+ quotaIsExceeded: false
624
+ };
625
+ spacesManager.spaceEnv.mockResolvedValue(space);
626
+ _files.isPathExists.mockResolvedValue(true);
627
+ _files.isPathIsDir.mockResolvedValue(true);
628
+ const gen = async function*() {
629
+ yield {
630
+ '/file1': [
631
+ false,
632
+ 1,
633
+ 2,
634
+ 3,
635
+ 'x'
636
+ ]
637
+ };
638
+ yield {
639
+ '/file2': [
640
+ true,
641
+ 0,
642
+ 2,
643
+ 4,
644
+ null
645
+ ]
646
+ };
647
+ };
648
+ jest.spyOn(service, 'parseSyncPath').mockImplementation(()=>gen());
649
+ await service.diff(user, 1, {
650
+ secureDiff: false
651
+ }, res);
652
+ expect(res.raw.writeHead).toHaveBeenCalledWith(200, {
653
+ 'Content-Type': 'text/plain; charset=utf-8',
654
+ 'Transfer-Encoding': 'chunked'
655
+ });
656
+ expect(res.raw.write).toHaveBeenCalledWith(`${JSON.stringify({
657
+ '/file1': [
658
+ false,
659
+ 1,
660
+ 2,
661
+ 3,
662
+ 'x'
663
+ ]
664
+ })}\n`);
665
+ expect(res.raw.write).toHaveBeenCalledWith(`${JSON.stringify({
666
+ '/file2': [
667
+ true,
668
+ 0,
669
+ 2,
670
+ 4,
671
+ null
672
+ ]
673
+ })}\n`);
674
+ expect(res.raw.write).toHaveBeenCalledWith(_sync.SYNC_DIFF_DONE);
675
+ expect(res.raw.end).toHaveBeenCalled();
676
+ });
677
+ it('should handle error during streaming and set status 500', async ()=>{
678
+ const res = makeReply();
679
+ const user = {
680
+ id: 1,
681
+ clientId: 9
682
+ };
683
+ syncQueries.getPathSettings.mockResolvedValue({
684
+ remotePath: '/base'
685
+ });
686
+ const space = {
687
+ realPath: '/base',
688
+ url: '/space',
689
+ quotaIsExceeded: false
690
+ };
691
+ spacesManager.spaceEnv.mockResolvedValue(space);
692
+ _files.isPathExists.mockResolvedValue(true);
693
+ _files.isPathIsDir.mockResolvedValue(true);
694
+ jest.spyOn(service, 'parseSyncPath').mockImplementation(()=>{
695
+ throw new Error('parse error');
696
+ });
697
+ await service.diff(user, 1, {}, res);
698
+ expect(res.raw.write).toHaveBeenCalledWith('parse error\n');
699
+ expect(res.status).toHaveBeenCalledWith(_common.HttpStatus.INTERNAL_SERVER_ERROR);
700
+ expect(res.raw.end).toHaveBeenCalled();
701
+ });
702
+ });
703
+ describe('internal parseFiles/analyzeFile coverage', ()=>{
704
+ const makeDirent = (name, parentPath, kind)=>({
705
+ name,
706
+ parentPath,
707
+ isDirectory: ()=>kind === 'dir',
708
+ isFile: ()=>kind === 'file'
709
+ });
710
+ it('should walk directory, ignore special files, filter by name/path, reuse snapshot checksum, and compute checksum when needed', async ()=>{
711
+ const base = '/base';
712
+ const ctx = {
713
+ regexBasePath: new RegExp('^/base'),
714
+ syncDiff: {
715
+ defaultFilters: new Set([
716
+ 'ignoredName'
717
+ ]),
718
+ pathFilters: /file2/,
719
+ secureDiff: true,
720
+ firstSync: false,
721
+ snapshot: new Map([
722
+ [
723
+ '/file3',
724
+ [
725
+ false,
726
+ 100,
727
+ 1000,
728
+ 33,
729
+ 'snaphash'
730
+ ]
731
+ ]
732
+ ])
733
+ }
734
+ };
735
+ fsPromises.readdir.mockImplementation(async (dir)=>{
736
+ if (dir === base) {
737
+ return [
738
+ makeDirent('special', base, 'other'),
739
+ makeDirent('dir1', base, 'dir'),
740
+ makeDirent('ignoredName', base, 'file'),
741
+ makeDirent('fileStatError', base, 'file'),
742
+ makeDirent('file2', base, 'file'),
743
+ makeDirent('file3', base, 'file'),
744
+ makeDirent('file4', base, 'file')
745
+ ];
746
+ }
747
+ if (dir === _nodepath.default.join(base, 'dir1')) return [];
748
+ return [];
749
+ });
750
+ const mtimeDate = new Date(1000 * 1000);
751
+ fsPromises.stat.mockImplementation(async (p)=>{
752
+ switch(p){
753
+ case _nodepath.default.join(base, 'dir1'):
754
+ return {
755
+ isDirectory: ()=>true,
756
+ isFile: ()=>false,
757
+ size: 0,
758
+ ino: 11,
759
+ mtime: mtimeDate
760
+ };
761
+ case _nodepath.default.join(base, 'fileStatError'):
762
+ throw new Error('stat fail');
763
+ case _nodepath.default.join(base, 'file2'):
764
+ return {
765
+ isDirectory: ()=>false,
766
+ isFile: ()=>true,
767
+ size: 10,
768
+ ino: 22,
769
+ mtime: mtimeDate
770
+ };
771
+ case _nodepath.default.join(base, 'file3'):
772
+ return {
773
+ isDirectory: ()=>false,
774
+ isFile: ()=>true,
775
+ size: 100,
776
+ ino: 33,
777
+ mtime: new Date(1000 * 1000)
778
+ };
779
+ case _nodepath.default.join(base, 'file4'):
780
+ return {
781
+ isDirectory: ()=>false,
782
+ isFile: ()=>true,
783
+ size: 200,
784
+ ino: 44,
785
+ mtime: new Date(2000 * 1000)
786
+ };
787
+ default:
788
+ return {
789
+ isDirectory: ()=>false,
790
+ isFile: ()=>false,
791
+ size: 0,
792
+ ino: 0,
793
+ mtime: new Date()
794
+ };
795
+ }
796
+ });
797
+ _files.checksumFile.mockResolvedValue('computed-hash');
798
+ const results = await collect(service.parseFiles(base, ctx));
799
+ const keys = results.map((o)=>Object.keys(o)[0]).sort();
800
+ expect(keys).toEqual([
801
+ '/dir1',
802
+ '/file2',
803
+ '/file3',
804
+ '/file4',
805
+ '/fileStatError'
806
+ ]);
807
+ const fileStatError = results.find((o)=>o['/fileStatError']);
808
+ expect(fileStatError?.['/fileStatError'][0]).toBe(_sync.F_SPECIAL_STAT.ERROR);
809
+ expect(fileStatError?.['/fileStatError'][1]).toContain('stat fail');
810
+ const filtered = results.find((o)=>o['/file2']);
811
+ expect(filtered?.['/file2'][0]).toBe(_sync.F_SPECIAL_STAT.FILTERED);
812
+ const reused = results.find((o)=>o['/file3'])?.['/file3'];
813
+ expect(reused[_sync.F_STAT.CHECKSUM]).toBe('snaphash');
814
+ expect(_files.checksumFile).toHaveBeenCalledTimes(1);
815
+ const computed = results.find((o)=>o['/file4'])?.['/file4'];
816
+ expect(computed[_sync.F_STAT.CHECKSUM]).toBe('computed-hash');
817
+ });
818
+ it('should throw a generic error when readdir fails', async ()=>{
819
+ fsPromises.readdir.mockRejectedValue(new Error('readdir fail'));
820
+ const ctx = {
821
+ regexBasePath: /./,
822
+ syncDiff: {
823
+ defaultFilters: new Set(),
824
+ secureDiff: false
825
+ }
826
+ };
827
+ const iter = service.parseFiles('/any', ctx);
828
+ await expect((async ()=>{
829
+ for await (const _ of iter){
830
+ /* consume */ }
831
+ })()).rejects.toThrow('Unable to parse path');
832
+ });
833
+ it('should return ERROR when checkSumFile throws during analyzeFile', async ()=>{
834
+ const base = '/base';
835
+ const dirent = {
836
+ name: 'badfile',
837
+ parentPath: base,
838
+ isDirectory: ()=>false,
839
+ isFile: ()=>true
840
+ };
841
+ fsPromises.readdir.mockImplementation(async (dir)=>dir === base ? [
842
+ dirent
843
+ ] : []);
844
+ fsPromises.stat.mockResolvedValue({
845
+ isDirectory: ()=>false,
846
+ isFile: ()=>true,
847
+ size: 10,
848
+ ino: 42,
849
+ mtime: new Date(1234 * 1000)
850
+ });
851
+ jest.spyOn(service, 'checkSumFile').mockRejectedValue(new Error('checksum fail'));
852
+ const ctx = {
853
+ regexBasePath: new RegExp('^/base'),
854
+ syncDiff: {
855
+ defaultFilters: new Set(),
856
+ pathFilters: undefined,
857
+ secureDiff: true,
858
+ firstSync: false,
859
+ snapshot: new Map()
860
+ }
861
+ };
862
+ const results = await collect(service.parseFiles(base, ctx));
863
+ expect(results).toHaveLength(1);
864
+ expect(results[0]).toHaveProperty('/badfile');
865
+ const out = results[0]['/badfile'];
866
+ expect(Array.isArray(out)).toBe(true);
867
+ expect(out[0]).toBe(_sync.F_SPECIAL_STAT.ERROR);
868
+ expect(String(out[1])).toContain('checksum fail');
869
+ });
38
870
  });
39
871
  });
40
872