@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.
- package/CHANGELOG.md +26 -0
- package/README.md +2 -1
- package/package.json +5 -5
- package/server/applications/comments/comments.controller.spec.js +103 -4
- package/server/applications/comments/comments.controller.spec.js.map +1 -1
- package/server/applications/comments/services/comments-manager.service.spec.js +409 -9
- package/server/applications/comments/services/comments-manager.service.spec.js.map +1 -1
- package/server/applications/files/adapters/files-indexer-mysql.service.spec.js +333 -0
- package/server/applications/files/adapters/files-indexer-mysql.service.spec.js.map +1 -0
- package/server/applications/files/constants/routes.js +6 -1
- package/server/applications/files/constants/routes.js.map +1 -1
- package/server/applications/files/files-only-office.controller.js +11 -0
- package/server/applications/files/files-only-office.controller.js.map +1 -1
- package/server/applications/files/files-only-office.controller.spec.js +97 -3
- package/server/applications/files/files-only-office.controller.spec.js.map +1 -1
- package/server/applications/files/files-tasks.controller.spec.js +91 -1
- package/server/applications/files/files-tasks.controller.spec.js.map +1 -1
- package/server/applications/files/files.controller.spec.js +268 -46
- package/server/applications/files/files.controller.spec.js.map +1 -1
- package/server/applications/files/guards/files-only-office.guard.spec.js +77 -1
- package/server/applications/files/guards/files-only-office.guard.spec.js.map +1 -1
- package/server/applications/files/services/files-only-office-manager.service.js +5 -0
- package/server/applications/files/services/files-only-office-manager.service.js.map +1 -1
- package/server/applications/links/links.controller.spec.js +91 -58
- package/server/applications/links/links.controller.spec.js.map +1 -1
- package/server/applications/links/services/links-manager.service.js +4 -6
- package/server/applications/links/services/links-manager.service.js.map +1 -1
- package/server/applications/links/services/links-manager.service.spec.js +378 -14
- package/server/applications/links/services/links-manager.service.spec.js.map +1 -1
- package/server/applications/links/services/links-queries.service.js +1 -1
- package/server/applications/links/services/links-queries.service.js.map +1 -1
- package/server/applications/notifications/notifications.controller.spec.js +56 -1
- package/server/applications/notifications/notifications.controller.spec.js.map +1 -1
- package/server/applications/notifications/services/notifications-manager.service.spec.js +461 -5
- package/server/applications/notifications/services/notifications-manager.service.spec.js.map +1 -1
- package/server/applications/shares/services/shares-manager.service.spec.js +590 -14
- package/server/applications/shares/services/shares-manager.service.spec.js.map +1 -1
- package/server/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.js +120 -0
- package/server/applications/sync/interceptors/sync-diff-gzip-body.interceptor.spec.js.map +1 -0
- package/server/applications/sync/services/sync-clients-manager.service.spec.js +548 -8
- package/server/applications/sync/services/sync-clients-manager.service.spec.js.map +1 -1
- package/server/applications/sync/services/sync-manager.service.spec.js +837 -5
- package/server/applications/sync/services/sync-manager.service.spec.js.map +1 -1
- package/server/applications/sync/services/sync-paths-manager.service.spec.js +900 -7
- package/server/applications/sync/services/sync-paths-manager.service.spec.js.map +1 -1
- package/server/applications/users/services/admin-users-manager.service.js +22 -24
- package/server/applications/users/services/admin-users-manager.service.js.map +1 -1
- package/server/applications/users/services/admin-users-manager.service.spec.js +763 -17
- package/server/applications/users/services/admin-users-manager.service.spec.js.map +1 -1
- package/server/applications/users/services/users-manager.service.js +1 -1
- package/server/applications/users/services/users-manager.service.js.map +1 -1
- package/server/applications/users/services/users-manager.service.spec.js +938 -49
- package/server/applications/users/services/users-manager.service.spec.js.map +1 -1
- package/server/applications/webdav/decorators/if-header.decorator.js +4 -1
- package/server/applications/webdav/decorators/if-header.decorator.js.map +1 -1
- package/server/applications/webdav/filters/webdav.filter.spec.js +77 -0
- package/server/applications/webdav/filters/webdav.filter.spec.js.map +1 -0
- package/server/applications/webdav/guards/webdav-protocol.guard.js +3 -7
- package/server/applications/webdav/guards/webdav-protocol.guard.js.map +1 -1
- package/server/applications/webdav/guards/webdav-protocol.guard.spec.js +580 -0
- package/server/applications/webdav/guards/webdav-protocol.guard.spec.js.map +1 -0
- package/server/applications/webdav/services/webdav-methods.service.spec.js +1582 -3
- package/server/applications/webdav/services/webdav-methods.service.spec.js.map +1 -1
- package/server/applications/webdav/services/webdav-spaces.service.spec.js +390 -2
- package/server/applications/webdav/services/webdav-spaces.service.spec.js.map +1 -1
- package/server/applications/webdav/webdav.controller.js +2 -2
- package/server/applications/webdav/webdav.controller.js.map +1 -1
- package/server/authentication/guards/auth-basic.guard.js.map +1 -1
- package/server/authentication/guards/auth-digest.guard.js +1 -2
- package/server/authentication/guards/auth-digest.guard.js.map +1 -1
- package/server/authentication/guards/auth-local.guard.js.map +1 -1
- package/server/infrastructure/context/interceptors/context.interceptor.spec.js +135 -0
- package/server/infrastructure/context/interceptors/context.interceptor.spec.js.map +1 -0
- package/static/3rdpartylicenses.txt +26 -26
- package/static/assets/pdfjs/build/pdf.mjs +1177 -255
- package/static/assets/pdfjs/build/pdf.mjs.map +1 -1
- package/static/assets/pdfjs/build/pdf.sandbox.mjs +25 -2
- package/static/assets/pdfjs/build/pdf.sandbox.mjs.map +1 -1
- package/static/assets/pdfjs/build/pdf.worker.mjs +140 -16
- package/static/assets/pdfjs/build/pdf.worker.mjs.map +1 -1
- package/static/assets/pdfjs/version +1 -1
- package/static/assets/pdfjs/web/debugger.css +31 -0
- package/static/assets/pdfjs/web/debugger.mjs +144 -2
- package/static/assets/pdfjs/web/images/comment-editButton.svg +6 -1
- package/static/assets/pdfjs/web/locale/ach/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/af/viewer.ftl +0 -71
- package/static/assets/pdfjs/web/locale/an/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/ast/viewer.ftl +0 -60
- package/static/assets/pdfjs/web/locale/az/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/be/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/bg/viewer.ftl +0 -37
- package/static/assets/pdfjs/web/locale/bn/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/bo/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/br/viewer.ftl +0 -37
- package/static/assets/pdfjs/web/locale/brx/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/bs/viewer.ftl +22 -0
- package/static/assets/pdfjs/web/locale/ca/viewer.ftl +0 -54
- package/static/assets/pdfjs/web/locale/cak/viewer.ftl +0 -54
- package/static/assets/pdfjs/web/locale/ckb/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/cs/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/cy/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/da/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/de/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/dsb/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/el/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/en-CA/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/en-GB/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/en-US/viewer.ftl +25 -0
- package/static/assets/pdfjs/web/locale/eo/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/es-AR/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/es-CL/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/es-MX/viewer.ftl +0 -6
- package/static/assets/pdfjs/web/locale/et/viewer.ftl +0 -57
- package/static/assets/pdfjs/web/locale/fa/viewer.ftl +0 -37
- package/static/assets/pdfjs/web/locale/ff/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/fi/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/fr/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/fy-NL/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/ga-IE/viewer.ftl +0 -71
- package/static/assets/pdfjs/web/locale/gd/viewer.ftl +0 -54
- package/static/assets/pdfjs/web/locale/gl/viewer.ftl +8 -0
- package/static/assets/pdfjs/web/locale/gn/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/gu-IN/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/he/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/hi-IN/viewer.ftl +0 -60
- package/static/assets/pdfjs/web/locale/hsb/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/hu/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/hy-AM/viewer.ftl +0 -49
- package/static/assets/pdfjs/web/locale/hye/viewer.ftl +0 -60
- package/static/assets/pdfjs/web/locale/ia/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/is/viewer.ftl +0 -3
- package/static/assets/pdfjs/web/locale/it/viewer.ftl +31 -0
- package/static/assets/pdfjs/web/locale/ja/viewer.ftl +8 -0
- package/static/assets/pdfjs/web/locale/ka/viewer.ftl +48 -10
- package/static/assets/pdfjs/web/locale/kab/viewer.ftl +5 -0
- package/static/assets/pdfjs/web/locale/kk/viewer.ftl +8 -0
- package/static/assets/pdfjs/web/locale/km/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/kn/viewer.ftl +0 -71
- package/static/assets/pdfjs/web/locale/ko/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/lij/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/lo/viewer.ftl +0 -54
- package/static/assets/pdfjs/web/locale/lt/viewer.ftl +0 -60
- package/static/assets/pdfjs/web/locale/ltg/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/lv/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/meh/viewer.ftl +0 -75
- package/static/assets/pdfjs/web/locale/mk/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/ml/viewer.ftl +0 -3
- package/static/assets/pdfjs/web/locale/mr/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/ms/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/my/viewer.ftl +0 -71
- package/static/assets/pdfjs/web/locale/nb-NO/viewer.ftl +44 -6
- package/static/assets/pdfjs/web/locale/ne-NP/viewer.ftl +0 -71
- package/static/assets/pdfjs/web/locale/nl/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/nn-NO/viewer.ftl +45 -1
- package/static/assets/pdfjs/web/locale/oc/viewer.ftl +0 -31
- package/static/assets/pdfjs/web/locale/pa-IN/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/pl/viewer.ftl +39 -1
- package/static/assets/pdfjs/web/locale/pt-BR/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/ro/viewer.ftl +355 -1
- package/static/assets/pdfjs/web/locale/ru/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/sat/viewer.ftl +0 -54
- package/static/assets/pdfjs/web/locale/sc/viewer.ftl +0 -38
- package/static/assets/pdfjs/web/locale/scn/viewer.ftl +0 -92
- package/static/assets/pdfjs/web/locale/sco/viewer.ftl +0 -60
- package/static/assets/pdfjs/web/locale/si/viewer.ftl +0 -51
- package/static/assets/pdfjs/web/locale/sk/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/skr/viewer.ftl +0 -27
- package/static/assets/pdfjs/web/locale/sl/viewer.ftl +8 -0
- package/static/assets/pdfjs/web/locale/son/viewer.ftl +0 -71
- package/static/assets/pdfjs/web/locale/sr/viewer.ftl +0 -33
- package/static/assets/pdfjs/web/locale/sv-SE/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/szl/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/ta/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/te/viewer.ftl +0 -60
- package/static/assets/pdfjs/web/locale/tg/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/tl/viewer.ftl +0 -63
- package/static/assets/pdfjs/web/locale/tr/viewer.ftl +40 -2
- package/static/assets/pdfjs/web/locale/trs/viewer.ftl +0 -72
- package/static/assets/pdfjs/web/locale/ur/viewer.ftl +0 -60
- package/static/assets/pdfjs/web/locale/uz/viewer.ftl +0 -71
- package/static/assets/pdfjs/web/locale/vi/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/wo/viewer.ftl +0 -77
- package/static/assets/pdfjs/web/locale/xh/viewer.ftl +0 -71
- package/static/assets/pdfjs/web/locale/zh-CN/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/locale/zh-TW/viewer.ftl +38 -0
- package/static/assets/pdfjs/web/viewer.css +649 -120
- package/static/assets/pdfjs/web/viewer.html +19 -0
- package/static/assets/pdfjs/web/viewer.mjs +489 -38
- package/static/assets/pdfjs/web/viewer.mjs.map +1 -1
- package/static/chunk-22EANI6R.js +1 -0
- package/static/{chunk-N2LYWNTC.js → chunk-2456KVFZ.js} +1 -1
- package/static/{chunk-YCINY2YI.js → chunk-2LVCLKCK.js} +1 -1
- package/static/{chunk-5YKWZT33.js → chunk-2V5S7DWD.js} +1 -1
- package/static/{chunk-3GC2BQZD.js → chunk-44YDXGNZ.js} +1 -1
- package/static/{chunk-W3QXNDI5.js → chunk-4LSJLWYV.js} +1 -1
- package/static/{chunk-T55FAU2O.js → chunk-4UT5VH7R.js} +1 -1
- package/static/{chunk-VMQMD36Z.js → chunk-5GOMMRRE.js} +1 -1
- package/static/{chunk-TXPODW5Q.js → chunk-5J4VRDKB.js} +1 -1
- package/static/{chunk-FQ4AFNGE.js → chunk-6PVKNZ7Q.js} +1 -1
- package/static/{chunk-XE5YHU5J.js → chunk-BIUNUYZ5.js} +1 -1
- package/static/{chunk-X43VWRFP.js → chunk-DFQKHCDR.js} +1 -1
- package/static/{chunk-TDQAEVZN.js → chunk-EE2TDTY4.js} +1 -1
- package/static/{chunk-MOVWEZ7J.js → chunk-ESNDJ5T6.js} +1 -1
- package/static/{chunk-TCFKH6K6.js → chunk-GDKKLLEU.js} +1 -1
- package/static/{chunk-VHYIXL7R.js → chunk-GSR2MCQG.js} +1 -1
- package/static/{chunk-LJIGRUEF.js → chunk-HR7KS5BR.js} +1 -1
- package/static/chunk-HW2H3ISM.js +559 -0
- package/static/{chunk-HZA7R43P.js → chunk-IMB3C547.js} +1 -1
- package/static/{chunk-B2Y2RNFP.js → chunk-J4ALHUDX.js} +1 -1
- package/static/{chunk-PF4K7MVG.js → chunk-KP6LSQTK.js} +1 -1
- package/static/{chunk-C23BPTJZ.js → chunk-LUZCOHFN.js} +1 -1
- package/static/{chunk-GBCYYDCI.js → chunk-MHSCCXVL.js} +1 -1
- package/static/{chunk-PY3BGNJN.js → chunk-OMRQYBXV.js} +1 -1
- package/static/chunk-P7CTJ5BG.js +27 -0
- package/static/{chunk-Y2CDUS4J.js → chunk-P7PX67IR.js} +4 -4
- package/static/{chunk-VMUOUCEI.js → chunk-PPO7DBVO.js} +1 -1
- package/static/{chunk-TTQ37MUV.js → chunk-RSS6GYNE.js} +1 -1
- package/static/{chunk-WWIC7UW3.js → chunk-SLGGINMR.js} +1 -1
- package/static/{chunk-KZQCFEPT.js → chunk-UHD5XD3G.js} +1 -1
- package/static/{chunk-TNW2CGK6.js → chunk-UPYYAJCJ.js} +1 -1
- package/static/{chunk-6F55D74O.js → chunk-VHYPQ3D4.js} +1 -1
- package/static/{chunk-DGVNNICG.js → chunk-YQSDS6BO.js} +1 -1
- package/static/{chunk-MTRNPGS4.js → chunk-ZC5NIT55.js} +1 -1
- package/static/index.html +1 -1
- package/static/main-FYD34UEC.js +7 -0
- package/static/chunk-RSNLYAN6.js +0 -560
- package/static/chunk-WHMS3PJ3.js +0 -24
- package/static/chunk-ZZ3LHYOY.js +0 -1
- 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
|
-
|
|
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
|