@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,51 +6,944 @@
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 _shared = require("../../../common/shared");
10
12
  const _contextmanagerservice = require("../../../infrastructure/context/services/context-manager.service");
11
13
  const _filesqueriesservice = require("../../files/services/files-queries.service");
14
+ const _files = require("../../files/utils/files");
12
15
  const _notificationsmanagerservice = require("../../notifications/services/notifications-manager.service");
13
16
  const _spacesmanagerservice = require("../../spaces/services/spaces-manager.service");
17
+ const _permissions = require("../../spaces/utils/permissions");
14
18
  const _usersqueriesservice = require("../../users/services/users-queries.service");
15
19
  const _syncpathsmanagerservice = require("./sync-paths-manager.service");
16
20
  const _syncqueriesservice = require("./sync-queries.service");
21
+ // Mock modules used directly inside SyncPathsManager
22
+ jest.mock('../../../common/shared', ()=>({
23
+ currentTimeStamp: jest.fn(()=>1000)
24
+ }));
25
+ jest.mock('../../files/utils/files', ()=>({
26
+ isPathExists: jest.fn(),
27
+ isPathIsDir: jest.fn(),
28
+ getProps: jest.fn(),
29
+ sanitizePath: jest.fn((p)=>p)
30
+ }));
31
+ jest.mock('../../spaces/utils/permissions', ()=>({
32
+ getEnvPermissions: jest.fn(()=>'server-perms')
33
+ }));
34
+ jest.mock('../constants/sync', ()=>({
35
+ SYNC_PATH_REPOSITORY: {
36
+ SPACES: [
37
+ 'spaces'
38
+ ],
39
+ PERSONAL: [
40
+ 'personal'
41
+ ],
42
+ SHARES: [
43
+ 'shares'
44
+ ]
45
+ }
46
+ }));
47
+ jest.mock('../../notifications/constants/notifications', ()=>({
48
+ NOTIFICATION_APP: {
49
+ SYNC: 'SYNC'
50
+ },
51
+ NOTIFICATION_APP_EVENT: {
52
+ SYNC: {
53
+ DELETE: 'DELETE',
54
+ CREATE: 'CREATE',
55
+ UPDATE: 'UPDATE'
56
+ }
57
+ }
58
+ }));
17
59
  describe(_syncpathsmanagerservice.SyncPathsManager.name, ()=>{
18
60
  let service;
19
- beforeAll(async ()=>{
61
+ let contextManager;
62
+ let spacesManager;
63
+ let usersQueries;
64
+ let filesQueries;
65
+ let notificationsManager;
66
+ let syncQueries;
67
+ const userWith = (clientId)=>({
68
+ id: 1,
69
+ clientId
70
+ });
71
+ const flush = ()=>new Promise((r)=>setImmediate(r));
72
+ beforeEach(async ()=>{
73
+ contextManager = {
74
+ get: jest.fn(()=>'http://origin.local')
75
+ };
76
+ spacesManager = {
77
+ spaceEnv: jest.fn()
78
+ };
79
+ usersQueries = {};
80
+ filesQueries = {
81
+ getSpaceFileId: jest.fn(),
82
+ getOrCreateSpaceFile: jest.fn()
83
+ };
84
+ notificationsManager = {
85
+ create: jest.fn().mockResolvedValue(undefined)
86
+ };
87
+ syncQueries = {
88
+ getClient: jest.fn(),
89
+ clientExistsForOwner: jest.fn(),
90
+ createPath: jest.fn(),
91
+ deletePath: jest.fn(),
92
+ getPaths: jest.fn(),
93
+ getPathSettings: jest.fn(),
94
+ updatePathSettings: jest.fn().mockResolvedValue(undefined),
95
+ clearCachePathSettings: jest.fn()
96
+ };
97
+ _files.isPathExists.mockReset();
98
+ _files.isPathIsDir.mockReset();
99
+ _files.getProps.mockReset();
100
+ _permissions.getEnvPermissions.mockReset().mockReturnValue('server-perms');
101
+ _shared.currentTimeStamp.mockReset().mockReturnValue(1000);
20
102
  const module = await _testing.Test.createTestingModule({
21
103
  providers: [
22
104
  _syncpathsmanagerservice.SyncPathsManager,
23
105
  {
24
106
  provide: _contextmanagerservice.ContextManager,
25
- useValue: {}
107
+ useValue: contextManager
26
108
  },
27
109
  {
28
110
  provide: _spacesmanagerservice.SpacesManager,
29
- useValue: {}
111
+ useValue: spacesManager
30
112
  },
31
113
  {
32
114
  provide: _usersqueriesservice.UsersQueries,
33
- useValue: {}
115
+ useValue: usersQueries
34
116
  },
35
117
  {
36
118
  provide: _filesqueriesservice.FilesQueries,
37
- useValue: {}
119
+ useValue: filesQueries
38
120
  },
39
121
  {
40
122
  provide: _notificationsmanagerservice.NotificationsManager,
41
- useValue: {}
123
+ useValue: notificationsManager
42
124
  },
43
125
  {
44
126
  provide: _syncqueriesservice.SyncQueries,
45
- useValue: {}
127
+ useValue: syncQueries
46
128
  }
47
129
  ]
48
130
  }).compile();
131
+ module.useLogger([
132
+ 'fatal'
133
+ ]);
49
134
  service = module.get(_syncpathsmanagerservice.SyncPathsManager);
50
135
  });
51
136
  it('should be defined', ()=>{
52
137
  expect(service).toBeDefined();
53
138
  });
139
+ describe('createPath', ()=>{
140
+ const baseReq = ()=>({
141
+ user: {
142
+ id: 1,
143
+ clientId: 'client-1'
144
+ },
145
+ params: {
146
+ '*': 'SPACES/alias/sub'
147
+ },
148
+ space: {
149
+ realPath: '/real/path',
150
+ quotaIsExceeded: false,
151
+ root: {
152
+ id: 1,
153
+ alias: 'alias'
154
+ },
155
+ inFilesRepository: true,
156
+ paths: [
157
+ 'sub'
158
+ ],
159
+ dbFile: {
160
+ ownerId: 1,
161
+ path: '.'
162
+ },
163
+ id: 10
164
+ }
165
+ });
166
+ it('should throw BAD_REQUEST when client id is missing', async ()=>{
167
+ const req = baseReq();
168
+ req.user.clientId = undefined;
169
+ await expect(service.createPath(req, {
170
+ remotePath: 'x'
171
+ })).rejects.toMatchObject({
172
+ status: _common.HttpStatus.BAD_REQUEST,
173
+ message: 'Client id is missing'
174
+ });
175
+ });
176
+ it('should throw INSUFFICIENT_STORAGE when space quota is exceeded', async ()=>{
177
+ const req = baseReq();
178
+ req.space.quotaIsExceeded = true;
179
+ await expect(service.createPath(req, {
180
+ remotePath: 'x'
181
+ })).rejects.toMatchObject({
182
+ status: _common.HttpStatus.INSUFFICIENT_STORAGE
183
+ });
184
+ });
185
+ it.each([
186
+ {
187
+ title: 'NOT_FOUND when remote path does not exist',
188
+ setup: ()=>_files.isPathExists.mockResolvedValue(false),
189
+ expected: {
190
+ status: _common.HttpStatus.NOT_FOUND,
191
+ message: 'Remote path not found : client/remote'
192
+ }
193
+ },
194
+ {
195
+ title: 'BAD_REQUEST when remote path is not a directory',
196
+ setup: ()=>(_files.isPathExists.mockResolvedValue(true), _files.isPathIsDir.mockResolvedValue(false)),
197
+ expected: {
198
+ status: _common.HttpStatus.BAD_REQUEST,
199
+ message: 'Remote path must be a directory'
200
+ }
201
+ },
202
+ {
203
+ title: 'NOT_FOUND when client is not found',
204
+ setup: ()=>(_files.isPathExists.mockResolvedValue(true), _files.isPathIsDir.mockResolvedValue(true), syncQueries.getClient.mockResolvedValue(null)),
205
+ expected: {
206
+ status: _common.HttpStatus.NOT_FOUND,
207
+ message: 'Client not found'
208
+ }
209
+ }
210
+ ])('should throw $title', async ({ setup, expected })=>{
211
+ const req = baseReq();
212
+ setup();
213
+ await expect(service.createPath(req, {
214
+ remotePath: 'client/remote'
215
+ })).rejects.toMatchObject(expected);
216
+ });
217
+ it('should create path and return id and permissions, overriding remotePath and permissions', async ()=>{
218
+ const req = baseReq();
219
+ _files.isPathExists.mockResolvedValue(true);
220
+ _files.isPathIsDir.mockResolvedValue(true);
221
+ _permissions.getEnvPermissions.mockReturnValue('env-perms');
222
+ syncQueries.getClient.mockResolvedValue({
223
+ id: 'client-1'
224
+ });
225
+ // Spy on private getDBProps to simplify
226
+ const getDBPropsSpy = jest.spyOn(service, 'getDBProps').mockResolvedValue({
227
+ ownerId: 1
228
+ });
229
+ syncQueries.createPath.mockResolvedValue(123);
230
+ const res = await service.createPath(req, {
231
+ remotePath: 'client/remote',
232
+ permissions: 'client-perms'
233
+ });
234
+ expect(res).toEqual({
235
+ id: 123,
236
+ permissions: 'env-perms'
237
+ });
238
+ expect(syncQueries.createPath).toHaveBeenCalledWith('client-1', {
239
+ ownerId: 1
240
+ }, expect.objectContaining({
241
+ remotePath: 'SPACES/alias/sub',
242
+ permissions: 'env-perms'
243
+ }));
244
+ getDBPropsSpy.mockRestore();
245
+ });
246
+ });
247
+ describe('deletePath', ()=>{
248
+ it.each([
249
+ {
250
+ user: {
251
+ id: 1,
252
+ clientId: undefined
253
+ },
254
+ id: 10,
255
+ status: _common.HttpStatus.BAD_REQUEST,
256
+ msg: 'Client id is missing'
257
+ },
258
+ {
259
+ user: {
260
+ id: 1,
261
+ clientId: 'c1'
262
+ },
263
+ id: 10,
264
+ status: _common.HttpStatus.FORBIDDEN
265
+ }
266
+ ])('should handle errors (status=$status)', async ({ user, id, status, msg })=>{
267
+ if (status === _common.HttpStatus.FORBIDDEN) syncQueries.clientExistsForOwner.mockResolvedValue(false);
268
+ await expect(service.deletePath(user, id)).rejects.toMatchObject(msg ? {
269
+ status,
270
+ message: msg
271
+ } : {
272
+ status
273
+ });
274
+ });
275
+ it('should catch errors from deletePath and throw BAD_REQUEST', async ()=>{
276
+ const user = {
277
+ id: 1,
278
+ clientId: 'c1'
279
+ };
280
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
281
+ syncQueries.deletePath.mockRejectedValue(new Error('db'));
282
+ await expect(service.deletePath(user, 10)).rejects.toMatchObject({
283
+ status: _common.HttpStatus.BAD_REQUEST,
284
+ message: 'Unable to remove path'
285
+ });
286
+ expect(syncQueries.deletePath).toHaveBeenCalledWith('c1', 10);
287
+ });
288
+ it('should delete path successfully when allowed', async ()=>{
289
+ const user = {
290
+ id: 1,
291
+ clientId: undefined
292
+ };
293
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
294
+ syncQueries.deletePath.mockResolvedValue(undefined);
295
+ await expect(service.deletePath(user, 10, 'cX')).resolves.toBeUndefined();
296
+ expect(syncQueries.deletePath).toHaveBeenCalledWith('cX', 10);
297
+ });
298
+ });
299
+ describe('updatePath', ()=>{
300
+ it('should throw FORBIDDEN when client does not belong to owner', async ()=>{
301
+ syncQueries.clientExistsForOwner.mockResolvedValue(false);
302
+ await expect(service.updatePath({
303
+ id: 1
304
+ }, 'c1', 5, {})).rejects.toMatchObject({
305
+ status: _common.HttpStatus.FORBIDDEN
306
+ });
307
+ });
308
+ it('should throw NOT_FOUND when path settings do not exist', async ()=>{
309
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
310
+ syncQueries.getPathSettings.mockResolvedValue(null);
311
+ await expect(service.updatePath({
312
+ id: 1
313
+ }, 'c1', 5, {})).rejects.toMatchObject({
314
+ status: _common.HttpStatus.NOT_FOUND,
315
+ message: 'Sync path not found'
316
+ });
317
+ });
318
+ it('should update path settings, set new timestamp, clear cache and return updated settings', async ()=>{
319
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
320
+ syncQueries.getPathSettings.mockResolvedValue({
321
+ id: 5,
322
+ timestamp: 500,
323
+ lastSync: 1,
324
+ remotePath: '/a',
325
+ permissions: 'p'
326
+ });
327
+ syncQueries.updatePathSettings.mockResolvedValue(undefined);
328
+ _shared.currentTimeStamp.mockReturnValue(4242);
329
+ const out = await service.updatePath({
330
+ id: 1
331
+ }, 'c1', 5, {
332
+ id: 666,
333
+ lastSync: 3,
334
+ permissions: 'new'
335
+ });
336
+ expect(syncQueries.updatePathSettings).toHaveBeenCalledWith('c1', 5, expect.objectContaining({
337
+ id: 5,
338
+ lastSync: 3,
339
+ timestamp: 4242,
340
+ permissions: 'new'
341
+ }));
342
+ expect(syncQueries.clearCachePathSettings).toHaveBeenCalledWith('c1', 5);
343
+ expect(out).toEqual(expect.objectContaining({
344
+ id: 5,
345
+ lastSync: 3,
346
+ timestamp: 4242,
347
+ permissions: 'new'
348
+ }));
349
+ });
350
+ it('should clear cache and throw INTERNAL_SERVER_ERROR when update fails', async ()=>{
351
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
352
+ syncQueries.getPathSettings.mockResolvedValue({
353
+ id: 5,
354
+ timestamp: 500,
355
+ lastSync: 1
356
+ });
357
+ syncQueries.updatePathSettings.mockRejectedValue(new Error('db'));
358
+ await expect(service.updatePath({
359
+ id: 1
360
+ }, 'c1', 5, {})).rejects.toMatchObject({
361
+ status: _common.HttpStatus.INTERNAL_SERVER_ERROR,
362
+ message: 'Unable to update path'
363
+ });
364
+ expect(syncQueries.clearCachePathSettings).toHaveBeenCalledWith('c1', 5);
365
+ });
366
+ });
367
+ describe('updatePaths', ()=>{
368
+ beforeEach(()=>{
369
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
370
+ spacesManager.spaceEnv.mockResolvedValue({
371
+ envPermissions: 'p'
372
+ });
373
+ });
374
+ it('should throw when client id is missing', async ()=>{
375
+ await expect(service.updatePaths(userWith(undefined), [])).rejects.toMatchObject({
376
+ status: _common.HttpStatus.BAD_REQUEST,
377
+ message: 'Client id is missing'
378
+ });
379
+ });
380
+ it('should throw FORBIDDEN when client does not belong to owner', async ()=>{
381
+ syncQueries.clientExistsForOwner.mockResolvedValue(false);
382
+ await expect(service.updatePaths(userWith('c1'), [])).rejects.toMatchObject({
383
+ status: _common.HttpStatus.FORBIDDEN
384
+ });
385
+ });
386
+ it('should mark client paths as deleted and notify when no corresponding server paths (server remotePath undefined)', async ()=>{
387
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
388
+ syncQueries.getPaths.mockResolvedValue([
389
+ {
390
+ id: 2,
391
+ settings: {
392
+ timestamp: 1,
393
+ lastSync: 1
394
+ },
395
+ remotePath: undefined
396
+ }
397
+ ]);
398
+ const clientPaths = [
399
+ {
400
+ id: 1,
401
+ remotePath: 'SPACES/a',
402
+ timestamp: 1,
403
+ lastSync: 1,
404
+ permissions: 'p'
405
+ }
406
+ ];
407
+ const res = await service.updatePaths(userWith('c1'), clientPaths);
408
+ expect(res.delete).toEqual([
409
+ 1
410
+ ]);
411
+ expect(notificationsManager.create).toHaveBeenCalledTimes(1);
412
+ });
413
+ it('should propagate spaceEnv errors as BAD_REQUEST', async ()=>{
414
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
415
+ syncQueries.getPaths.mockResolvedValue([
416
+ {
417
+ id: 1,
418
+ settings: {
419
+ timestamp: 1,
420
+ lastSync: 1
421
+ },
422
+ remotePath: 'SPACES/x'
423
+ }
424
+ ]);
425
+ spacesManager.spaceEnv.mockRejectedValue(new Error('boom'));
426
+ await expect(service.updatePaths(userWith('c1'), [
427
+ {
428
+ id: 1,
429
+ remotePath: 'SPACES/x',
430
+ timestamp: 1,
431
+ lastSync: 1,
432
+ permissions: 'p'
433
+ }
434
+ ])).rejects.toMatchObject({
435
+ status: _common.HttpStatus.BAD_REQUEST,
436
+ message: 'boom'
437
+ });
438
+ });
439
+ it('should skip server path when space is null and mark client item as deleted', async ()=>{
440
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
441
+ syncQueries.getPaths.mockResolvedValue([
442
+ {
443
+ id: 2,
444
+ settings: {
445
+ timestamp: 1,
446
+ lastSync: 1
447
+ },
448
+ remotePath: 'SPACES/x'
449
+ }
450
+ ]);
451
+ spacesManager.spaceEnv.mockResolvedValue(null);
452
+ const res = await service.updatePaths(userWith('c1'), [
453
+ {
454
+ id: 2,
455
+ remotePath: 'SPACES/x',
456
+ timestamp: 1,
457
+ lastSync: 1,
458
+ permissions: 'p'
459
+ }
460
+ ]);
461
+ expect(res.delete).toEqual([
462
+ 2
463
+ ]);
464
+ expect(notificationsManager.create).toHaveBeenCalledTimes(1);
465
+ });
466
+ it('should add server-only path to client with server permissions', async ()=>{
467
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
468
+ syncQueries.getPaths.mockResolvedValue([
469
+ {
470
+ id: 3,
471
+ settings: {
472
+ timestamp: 1,
473
+ lastSync: 1
474
+ },
475
+ remotePath: 'SPACES/x'
476
+ }
477
+ ]);
478
+ spacesManager.spaceEnv.mockResolvedValue({
479
+ envPermissions: 'perm-xyz'
480
+ });
481
+ const res = await service.updatePaths(userWith('c1'), []);
482
+ expect(res.add).toHaveLength(1);
483
+ expect(res.add[0]).toEqual(expect.objectContaining({
484
+ id: 3,
485
+ remotePath: 'SPACES/x',
486
+ permissions: 'perm-xyz'
487
+ }));
488
+ });
489
+ it('should update server settings from client when client is newer (no hasUpdates)', async ()=>{
490
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
491
+ syncQueries.getPaths.mockResolvedValue([
492
+ {
493
+ id: 5,
494
+ settings: {
495
+ timestamp: 1,
496
+ lastSync: 1,
497
+ foo: 'server'
498
+ },
499
+ remotePath: 'SPACES/x'
500
+ }
501
+ ]);
502
+ spacesManager.spaceEnv.mockResolvedValue({
503
+ envPermissions: 'p'
504
+ });
505
+ const client = [
506
+ {
507
+ id: 5,
508
+ timestamp: 10,
509
+ lastSync: 2,
510
+ remotePath: 'SPACES/x',
511
+ permissions: 'p',
512
+ foo: 'client'
513
+ }
514
+ ];
515
+ await service.updatePaths(userWith('c1'), client);
516
+ expect(syncQueries.updatePathSettings).toHaveBeenCalledWith('c1', 5, expect.objectContaining({
517
+ foo: 'client',
518
+ lastSync: 2
519
+ }));
520
+ // No client update instructions because hasUpdates=false and serverNewer=false
521
+ });
522
+ it('should push server-newer updates to client and also remotePath/permissions corrections', async ()=>{
523
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
524
+ syncQueries.getPaths.mockResolvedValue([
525
+ {
526
+ id: 7,
527
+ settings: {
528
+ timestamp: 20,
529
+ lastSync: 5,
530
+ srv: true
531
+ },
532
+ remotePath: 'SPACES/correct'
533
+ }
534
+ ]);
535
+ spacesManager.spaceEnv.mockResolvedValue({
536
+ envPermissions: 'permX'
537
+ });
538
+ const client = [
539
+ {
540
+ id: 7,
541
+ timestamp: 10,
542
+ lastSync: 5,
543
+ remotePath: 'SPACES/wrong',
544
+ permissions: 'old'
545
+ }
546
+ ];
547
+ const res = await service.updatePaths(userWith('c1'), client);
548
+ // Should have two client updates: one full server settings + corrections, and one corrections-only
549
+ expect(res.update).toEqual(expect.arrayContaining([
550
+ expect.objectContaining({
551
+ id: 7,
552
+ srv: true,
553
+ remotePath: 'SPACES/correct',
554
+ permissions: 'permX'
555
+ }),
556
+ expect.objectContaining({
557
+ id: 7,
558
+ remotePath: 'SPACES/correct',
559
+ permissions: 'permX'
560
+ })
561
+ ]));
562
+ expect(syncQueries.updatePathSettings).toHaveBeenCalledWith('c1', 7, expect.objectContaining({
563
+ lastSync: 5
564
+ }));
565
+ // clear cache called for each update instruction
566
+ expect(syncQueries.clearCachePathSettings).toHaveBeenCalledTimes(res.update.length);
567
+ for (const u of res.update){
568
+ expect(syncQueries.clearCachePathSettings).toHaveBeenCalledWith('c1', u.id);
569
+ }
570
+ });
571
+ it('should trigger update when lastSync differs even if timestamps and settings match', async ()=>{
572
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
573
+ syncQueries.getPaths.mockResolvedValue([
574
+ {
575
+ id: 9,
576
+ settings: {
577
+ timestamp: 10,
578
+ lastSync: 1,
579
+ flag: 'S'
580
+ },
581
+ remotePath: 'SPACES/x'
582
+ }
583
+ ]);
584
+ spacesManager.spaceEnv.mockResolvedValue({
585
+ envPermissions: 'p'
586
+ });
587
+ const client = [
588
+ {
589
+ id: 9,
590
+ timestamp: 10,
591
+ lastSync: 2,
592
+ remotePath: 'SPACES/x',
593
+ permissions: 'p',
594
+ flag: 'S'
595
+ }
596
+ ];
597
+ await service.updatePaths(userWith('c1'), client);
598
+ expect(syncQueries.updatePathSettings).toHaveBeenCalledWith('c1', 9, expect.objectContaining({
599
+ lastSync: 2
600
+ }));
601
+ });
602
+ it('should perform no changes when client and server are identical (no-op branch)', async ()=>{
603
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
604
+ syncQueries.getPaths.mockResolvedValue([
605
+ {
606
+ id: 12,
607
+ settings: {
608
+ timestamp: 5,
609
+ lastSync: 7,
610
+ flag: 'A'
611
+ },
612
+ remotePath: 'SPACES/x'
613
+ }
614
+ ]);
615
+ spacesManager.spaceEnv.mockResolvedValue({
616
+ envPermissions: 'p'
617
+ });
618
+ const client = [
619
+ {
620
+ id: 12,
621
+ timestamp: 5,
622
+ lastSync: 7,
623
+ remotePath: 'SPACES/x',
624
+ permissions: 'p',
625
+ flag: 'A'
626
+ }
627
+ ];
628
+ const res = await service.updatePaths(userWith('c1'), client);
629
+ expect(res).toEqual({
630
+ add: [],
631
+ update: [],
632
+ delete: []
633
+ });
634
+ expect(syncQueries.updatePathSettings).not.toHaveBeenCalled();
635
+ });
636
+ it('should correct client info while keeping server settings when timestamps are equal (hasUpdates=true)', async ()=>{
637
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
638
+ // Server has srv='S', client has wrong remotePath/permissions and extra clientOnly flag
639
+ syncQueries.getPaths.mockResolvedValue([
640
+ {
641
+ id: 13,
642
+ settings: {
643
+ timestamp: 5,
644
+ lastSync: 1,
645
+ srv: 'S'
646
+ },
647
+ remotePath: 'SPACES/correct'
648
+ }
649
+ ]);
650
+ spacesManager.spaceEnv.mockResolvedValue({
651
+ envPermissions: 'permX'
652
+ });
653
+ const client = [
654
+ {
655
+ id: 13,
656
+ timestamp: 5,
657
+ lastSync: 1,
658
+ remotePath: 'SPACES/wrong',
659
+ permissions: 'old',
660
+ clientOnly: 'C'
661
+ }
662
+ ];
663
+ const res = await service.updatePaths(userWith('c1'), client);
664
+ // Server uses its own settings base (srv: 'S') with corrections applied
665
+ expect(syncQueries.updatePathSettings).toHaveBeenCalledWith('c1', 13, expect.objectContaining({
666
+ srv: 'S',
667
+ lastSync: 1,
668
+ remotePath: 'SPACES/correct',
669
+ permissions: 'permX'
670
+ }));
671
+ // Client should receive corrections for remotePath and permissions
672
+ expect(res.update).toEqual(expect.arrayContaining([
673
+ expect.objectContaining({
674
+ id: 13,
675
+ remotePath: 'SPACES/correct',
676
+ permissions: 'permX'
677
+ })
678
+ ]));
679
+ });
680
+ it('should log error when updatePathSettings rejects inside updatePaths', async ()=>{
681
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
682
+ syncQueries.getPaths.mockResolvedValue([
683
+ {
684
+ id: 11,
685
+ settings: {
686
+ timestamp: 1,
687
+ lastSync: 1
688
+ },
689
+ remotePath: 'SPACES/x'
690
+ }
691
+ ]);
692
+ spacesManager.spaceEnv.mockResolvedValue({
693
+ envPermissions: 'p'
694
+ });
695
+ // Force client newer to trigger updatePathSettings
696
+ const client = [
697
+ {
698
+ id: 11,
699
+ timestamp: 10,
700
+ lastSync: 2,
701
+ remotePath: 'SPACES/x',
702
+ permissions: 'p'
703
+ }
704
+ ];
705
+ syncQueries.updatePathSettings.mockRejectedValueOnce(new Error('db-fail'));
706
+ const loggerSpy = jest.spyOn(service.logger, 'error').mockImplementation(()=>undefined);
707
+ await service.updatePaths(userWith('c1'), client);
708
+ // wait for microtasks to ensure .catch executed
709
+ await flush();
710
+ expect(loggerSpy).toHaveBeenCalled();
711
+ expect(loggerSpy.mock.calls.some(([msg])=>String(msg).includes('updatePaths'))).toBe(true);
712
+ });
713
+ it('should catch notify failure at updatePaths level when building notification fails', async ()=>{
714
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
715
+ // No server paths: will mark client path as deleted and call notify
716
+ syncQueries.getPaths.mockResolvedValue([]);
717
+ const loggerSpy = jest.spyOn(service.logger, 'error').mockImplementation(()=>undefined);
718
+ // BAD first segment to make notify fail while building URL (before notificationsManager.create)
719
+ const client = [
720
+ {
721
+ id: 1,
722
+ remotePath: 'BAD/a/b',
723
+ timestamp: 1,
724
+ lastSync: 1,
725
+ permissions: 'p'
726
+ }
727
+ ];
728
+ const res = await service.updatePaths(userWith('c1'), client);
729
+ await flush();
730
+ expect(res.delete).toEqual([
731
+ 1
732
+ ]);
733
+ expect(loggerSpy).toHaveBeenCalled();
734
+ expect(loggerSpy.mock.calls.some(([msg])=>String(msg).includes('updatePaths'))).toBe(true);
735
+ // create not called because we failed before reaching it
736
+ expect(notificationsManager.create).not.toHaveBeenCalled();
737
+ });
738
+ it('should catch error inside notify when notifications creation fails', async ()=>{
739
+ syncQueries.clientExistsForOwner.mockResolvedValue(true);
740
+ syncQueries.getPaths.mockResolvedValue([]);
741
+ // Valid remotePath to build URL correctly
742
+ const client = [
743
+ {
744
+ id: 2,
745
+ remotePath: 'SPACES/a',
746
+ timestamp: 1,
747
+ lastSync: 1,
748
+ permissions: 'p'
749
+ }
750
+ ];
751
+ const loggerSpy = jest.spyOn(service.logger, 'error').mockImplementation(()=>undefined);
752
+ notificationsManager.create.mockRejectedValueOnce(new Error('notify-fail'));
753
+ const res = await service.updatePaths(userWith('c1'), client);
754
+ await flush();
755
+ expect(res.delete).toEqual([
756
+ 2
757
+ ]);
758
+ expect(loggerSpy).toHaveBeenCalled();
759
+ // error comes from notify() catch
760
+ expect(loggerSpy.mock.calls.some(([msg])=>String(msg).includes('notify'))).toBe(true);
761
+ });
762
+ });
763
+ describe('getDBProps (private) branches', ()=>{
764
+ it('should throw BAD_REQUEST for shares list selection', async ()=>{
765
+ await expect(service.getDBProps({
766
+ inSharesList: true
767
+ })).rejects.toMatchObject({
768
+ status: _common.HttpStatus.BAD_REQUEST,
769
+ message: 'Sync all shares is not supported, you must select a sub-directory'
770
+ });
771
+ });
772
+ it('should return ownerId only for personal space at root', async ()=>{
773
+ const res = await service.getDBProps({
774
+ inSharesList: false,
775
+ inPersonalSpace: true,
776
+ paths: [],
777
+ dbFile: {
778
+ ownerId: 42
779
+ }
780
+ });
781
+ expect(res).toEqual({
782
+ ownerId: 42
783
+ });
784
+ });
785
+ it('should return ownerId and fileId for personal space subdir', async ()=>{
786
+ const getOrCreateFileIdSpy = jest.spyOn(service, 'getOrCreateFileId').mockResolvedValue(77);
787
+ const res = await service.getDBProps({
788
+ inPersonalSpace: true,
789
+ paths: [
790
+ 'sub'
791
+ ],
792
+ dbFile: {
793
+ ownerId: 42
794
+ }
795
+ });
796
+ expect(res).toEqual({
797
+ ownerId: 42,
798
+ fileId: 77
799
+ });
800
+ getOrCreateFileIdSpy.mockRestore();
801
+ });
802
+ it('should throw BAD_REQUEST for whole files repository without alias', async ()=>{
803
+ await expect(service.getDBProps({
804
+ inFilesRepository: true,
805
+ root: {
806
+ alias: null
807
+ },
808
+ paths: []
809
+ })).rejects.toMatchObject({
810
+ status: _common.HttpStatus.BAD_REQUEST,
811
+ message: 'Sync all space is not yet supported, you must select a sub-directory'
812
+ });
813
+ });
814
+ it('should return spaceId and rootId for files repository root selection', async ()=>{
815
+ const res = await service.getDBProps({
816
+ inFilesRepository: true,
817
+ id: 5,
818
+ root: {
819
+ id: 3,
820
+ alias: 'x'
821
+ },
822
+ paths: []
823
+ });
824
+ expect(res).toEqual({
825
+ spaceId: 5,
826
+ spaceRootId: 3
827
+ });
828
+ });
829
+ it('should return spaceId, rootId and fileId for files repository subdir or null root', async ()=>{
830
+ const getOrCreateFileIdSpy = jest.spyOn(service, 'getOrCreateFileId').mockResolvedValue(88);
831
+ const res = await service.getDBProps({
832
+ inFilesRepository: true,
833
+ id: 5,
834
+ root: {
835
+ id: 3,
836
+ alias: 'x'
837
+ },
838
+ paths: [
839
+ 'sub'
840
+ ]
841
+ });
842
+ expect(res).toEqual({
843
+ spaceId: 5,
844
+ spaceRootId: 3,
845
+ fileId: 88
846
+ });
847
+ getOrCreateFileIdSpy.mockRestore();
848
+ });
849
+ it('should return spaceId and null spaceRootId plus fileId when files repository root has no id', async ()=>{
850
+ const getOrCreateFileIdSpy = jest.spyOn(service, 'getOrCreateFileId').mockResolvedValue(90);
851
+ const res = await service.getDBProps({
852
+ inFilesRepository: true,
853
+ id: 6,
854
+ root: {
855
+ id: undefined,
856
+ alias: 'x'
857
+ },
858
+ paths: []
859
+ });
860
+ expect(res).toEqual({
861
+ spaceId: 6,
862
+ spaceRootId: null,
863
+ fileId: 90
864
+ });
865
+ getOrCreateFileIdSpy.mockRestore();
866
+ });
867
+ it('should return shareId only for shares repository root', async ()=>{
868
+ const res = await service.getDBProps({
869
+ inSharesList: false,
870
+ inPersonalSpace: false,
871
+ inFilesRepository: false,
872
+ inSharesRepository: true,
873
+ id: 9,
874
+ paths: []
875
+ });
876
+ expect(res).toEqual({
877
+ shareId: 9
878
+ });
879
+ });
880
+ it('should return shareId and fileId for shares repository subdir', async ()=>{
881
+ const getOrCreateFileIdSpy = jest.spyOn(service, 'getOrCreateFileId').mockResolvedValue(55);
882
+ const res = await service.getDBProps({
883
+ inSharesList: false,
884
+ inPersonalSpace: false,
885
+ inFilesRepository: false,
886
+ inSharesRepository: true,
887
+ id: 9,
888
+ paths: [
889
+ 'sub'
890
+ ]
891
+ });
892
+ expect(res).toEqual({
893
+ shareId: 9,
894
+ fileId: 55
895
+ });
896
+ getOrCreateFileIdSpy.mockRestore();
897
+ });
898
+ it('should return undefined when no space flags match (no branch taken)', async ()=>{
899
+ const res = await service.getDBProps({
900
+ inSharesList: false,
901
+ inPersonalSpace: false,
902
+ inFilesRepository: false,
903
+ inSharesRepository: false,
904
+ paths: []
905
+ });
906
+ expect(res).toBeUndefined();
907
+ });
908
+ });
909
+ describe('getOrCreateFileId (private) branches', ()=>{
910
+ it('should return existing file id without creation', async ()=>{
911
+ ;
912
+ _files.getProps.mockResolvedValue({
913
+ name: 'file'
914
+ });
915
+ filesQueries.getSpaceFileId.mockResolvedValue(101);
916
+ const id = await service.getOrCreateFileId({
917
+ realPath: '/rp',
918
+ dbFile: {
919
+ path: '.'
920
+ }
921
+ });
922
+ expect(id).toBe(101);
923
+ expect(filesQueries.getOrCreateSpaceFile).not.toHaveBeenCalled();
924
+ });
925
+ it('should create file when not exists and return its id', async ()=>{
926
+ ;
927
+ _files.getProps.mockResolvedValue({
928
+ id: 999,
929
+ name: 'file'
930
+ });
931
+ filesQueries.getSpaceFileId.mockResolvedValue(0);
932
+ filesQueries.getOrCreateSpaceFile.mockResolvedValue(202);
933
+ const id = await service.getOrCreateFileId({
934
+ realPath: '/rp',
935
+ dbFile: {
936
+ path: '.'
937
+ }
938
+ });
939
+ expect(id).toBe(202);
940
+ expect(filesQueries.getOrCreateSpaceFile).toHaveBeenCalledWith(0, expect.objectContaining({
941
+ id: undefined
942
+ }), {
943
+ path: '.'
944
+ });
945
+ });
946
+ });
54
947
  });
55
948
 
56
949
  //# sourceMappingURL=sync-paths-manager.service.spec.js.map