@sync-in/server
Version:
The secure, open-source platform for file storage, sharing, collaboration, and sync
949 lines (948 loc) • 36.9 kB
JavaScript
/*
* Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>
* This file is part of Sync-in | The open source file sync and share solution
* See the LICENSE file for licensing details
*/ "use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
const _common = require("@nestjs/common");
const _testing = require("@nestjs/testing");
const _shared = require("../../../common/shared");
const _contextmanagerservice = require("../../../infrastructure/context/services/context-manager.service");
const _filesqueriesservice = require("../../files/services/files-queries.service");
const _files = require("../../files/utils/files");
const _notificationsmanagerservice = require("../../notifications/services/notifications-manager.service");
const _spacesmanagerservice = require("../../spaces/services/spaces-manager.service");
const _permissions = require("../../spaces/utils/permissions");
const _usersqueriesservice = require("../../users/services/users-queries.service");
const _syncpathsmanagerservice = require("./sync-paths-manager.service");
const _syncqueriesservice = require("./sync-queries.service");
// Mock modules used directly inside SyncPathsManager
jest.mock('../../../common/shared', ()=>({
currentTimeStamp: jest.fn(()=>1000)
}));
jest.mock('../../files/utils/files', ()=>({
isPathExists: jest.fn(),
isPathIsDir: jest.fn(),
getProps: jest.fn(),
sanitizePath: jest.fn((p)=>p)
}));
jest.mock('../../spaces/utils/permissions', ()=>({
getEnvPermissions: jest.fn(()=>'server-perms')
}));
jest.mock('../constants/sync', ()=>({
SYNC_PATH_REPOSITORY: {
SPACES: [
'spaces'
],
PERSONAL: [
'personal'
],
SHARES: [
'shares'
]
}
}));
jest.mock('../../notifications/constants/notifications', ()=>({
NOTIFICATION_APP: {
SYNC: 'SYNC'
},
NOTIFICATION_APP_EVENT: {
SYNC: {
DELETE: 'DELETE',
CREATE: 'CREATE',
UPDATE: 'UPDATE'
}
}
}));
describe(_syncpathsmanagerservice.SyncPathsManager.name, ()=>{
let service;
let contextManager;
let spacesManager;
let usersQueries;
let filesQueries;
let notificationsManager;
let syncQueries;
const userWith = (clientId)=>({
id: 1,
clientId
});
const flush = ()=>new Promise((r)=>setImmediate(r));
beforeEach(async ()=>{
contextManager = {
headerOriginUrl: jest.fn(()=>'http://origin.local')
};
spacesManager = {
spaceEnv: jest.fn()
};
usersQueries = {};
filesQueries = {
getSpaceFileId: jest.fn(),
getOrCreateSpaceFile: jest.fn()
};
notificationsManager = {
create: jest.fn().mockResolvedValue(undefined)
};
syncQueries = {
getClient: jest.fn(),
clientExistsForOwner: jest.fn(),
createPath: jest.fn(),
deletePath: jest.fn(),
getPaths: jest.fn(),
getPathSettings: jest.fn(),
updatePathSettings: jest.fn().mockResolvedValue(undefined),
clearCachePathSettings: jest.fn()
};
_files.isPathExists.mockReset();
_files.isPathIsDir.mockReset();
_files.getProps.mockReset();
_permissions.getEnvPermissions.mockReset().mockReturnValue('server-perms');
_shared.currentTimeStamp.mockReset().mockReturnValue(1000);
const module = await _testing.Test.createTestingModule({
providers: [
_syncpathsmanagerservice.SyncPathsManager,
{
provide: _contextmanagerservice.ContextManager,
useValue: contextManager
},
{
provide: _spacesmanagerservice.SpacesManager,
useValue: spacesManager
},
{
provide: _usersqueriesservice.UsersQueries,
useValue: usersQueries
},
{
provide: _filesqueriesservice.FilesQueries,
useValue: filesQueries
},
{
provide: _notificationsmanagerservice.NotificationsManager,
useValue: notificationsManager
},
{
provide: _syncqueriesservice.SyncQueries,
useValue: syncQueries
}
]
}).compile();
module.useLogger([
'fatal'
]);
service = module.get(_syncpathsmanagerservice.SyncPathsManager);
});
it('should be defined', ()=>{
expect(service).toBeDefined();
});
describe('createPath', ()=>{
const baseReq = ()=>({
user: {
id: 1,
clientId: 'client-1'
},
params: {
'*': 'SPACES/alias/sub'
},
space: {
realPath: '/real/path',
quotaIsExceeded: false,
root: {
id: 1,
alias: 'alias'
},
inFilesRepository: true,
paths: [
'sub'
],
dbFile: {
ownerId: 1,
path: '.'
},
id: 10
}
});
it('should throw BAD_REQUEST when client id is missing', async ()=>{
const req = baseReq();
req.user.clientId = undefined;
await expect(service.createPath(req, {
remotePath: 'x'
})).rejects.toMatchObject({
status: _common.HttpStatus.BAD_REQUEST,
message: 'Client id is missing'
});
});
it('should throw INSUFFICIENT_STORAGE when storage quota is exceeded', async ()=>{
const req = baseReq();
req.space.quotaIsExceeded = true;
await expect(service.createPath(req, {
remotePath: 'x'
})).rejects.toMatchObject({
status: _common.HttpStatus.INSUFFICIENT_STORAGE
});
});
it.each([
{
title: 'NOT_FOUND when remote path does not exist',
setup: ()=>_files.isPathExists.mockResolvedValue(false),
expected: {
status: _common.HttpStatus.NOT_FOUND,
message: 'Remote path not found : client/remote'
}
},
{
title: 'BAD_REQUEST when remote path is not a directory',
setup: ()=>(_files.isPathExists.mockResolvedValue(true), _files.isPathIsDir.mockResolvedValue(false)),
expected: {
status: _common.HttpStatus.BAD_REQUEST,
message: 'Remote path must be a directory'
}
},
{
title: 'NOT_FOUND when client is not found',
setup: ()=>(_files.isPathExists.mockResolvedValue(true), _files.isPathIsDir.mockResolvedValue(true), syncQueries.getClient.mockResolvedValue(null)),
expected: {
status: _common.HttpStatus.NOT_FOUND,
message: 'Client not found'
}
}
])('should throw $title', async ({ setup, expected })=>{
const req = baseReq();
setup();
await expect(service.createPath(req, {
remotePath: 'client/remote'
})).rejects.toMatchObject(expected);
});
it('should create path and return id and permissions, overriding remotePath and permissions', async ()=>{
const req = baseReq();
_files.isPathExists.mockResolvedValue(true);
_files.isPathIsDir.mockResolvedValue(true);
_permissions.getEnvPermissions.mockReturnValue('env-perms');
syncQueries.getClient.mockResolvedValue({
id: 'client-1'
});
// Spy on private getDBProps to simplify
const getDBPropsSpy = jest.spyOn(service, 'getDBProps').mockResolvedValue({
ownerId: 1
});
syncQueries.createPath.mockResolvedValue(123);
const res = await service.createPath(req, {
remotePath: 'client/remote',
permissions: 'client-perms'
});
expect(res).toEqual({
id: 123,
permissions: 'env-perms'
});
expect(syncQueries.createPath).toHaveBeenCalledWith('client-1', {
ownerId: 1
}, expect.objectContaining({
remotePath: 'SPACES/alias/sub',
permissions: 'env-perms'
}));
getDBPropsSpy.mockRestore();
});
});
describe('deletePath', ()=>{
it.each([
{
user: {
id: 1,
clientId: undefined
},
id: 10,
status: _common.HttpStatus.BAD_REQUEST,
msg: 'Client id is missing'
},
{
user: {
id: 1,
clientId: 'c1'
},
id: 10,
status: _common.HttpStatus.FORBIDDEN
}
])('should handle errors (status=$status)', async ({ user, id, status, msg })=>{
if (status === _common.HttpStatus.FORBIDDEN) syncQueries.clientExistsForOwner.mockResolvedValue(false);
await expect(service.deletePath(user, id)).rejects.toMatchObject(msg ? {
status,
message: msg
} : {
status
});
});
it('should catch errors from deletePath and throw BAD_REQUEST', async ()=>{
const user = {
id: 1,
clientId: 'c1'
};
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.deletePath.mockRejectedValue(new Error('db'));
await expect(service.deletePath(user, 10)).rejects.toMatchObject({
status: _common.HttpStatus.BAD_REQUEST,
message: 'Unable to remove path'
});
expect(syncQueries.deletePath).toHaveBeenCalledWith('c1', 10);
});
it('should delete path successfully when allowed', async ()=>{
const user = {
id: 1,
clientId: undefined
};
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.deletePath.mockResolvedValue(undefined);
await expect(service.deletePath(user, 10, 'cX')).resolves.toBeUndefined();
expect(syncQueries.deletePath).toHaveBeenCalledWith('cX', 10);
});
});
describe('updatePath', ()=>{
it('should throw FORBIDDEN when client does not belong to owner', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(false);
await expect(service.updatePath({
id: 1
}, 'c1', 5, {})).rejects.toMatchObject({
status: _common.HttpStatus.FORBIDDEN
});
});
it('should throw NOT_FOUND when path settings do not exist', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPathSettings.mockResolvedValue(null);
await expect(service.updatePath({
id: 1
}, 'c1', 5, {})).rejects.toMatchObject({
status: _common.HttpStatus.NOT_FOUND,
message: 'Sync path not found'
});
});
it('should update path settings, set new timestamp, clear cache and return updated settings', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPathSettings.mockResolvedValue({
id: 5,
timestamp: 500,
lastSync: 1,
remotePath: '/a',
permissions: 'p'
});
syncQueries.updatePathSettings.mockResolvedValue(undefined);
_shared.currentTimeStamp.mockReturnValue(4242);
const out = await service.updatePath({
id: 1
}, 'c1', 5, {
id: 666,
lastSync: 3,
permissions: 'new'
});
expect(syncQueries.updatePathSettings).toHaveBeenCalledWith('c1', 5, expect.objectContaining({
id: 5,
lastSync: 3,
timestamp: 4242,
permissions: 'new'
}));
expect(syncQueries.clearCachePathSettings).toHaveBeenCalledWith('c1', 5);
expect(out).toEqual(expect.objectContaining({
id: 5,
lastSync: 3,
timestamp: 4242,
permissions: 'new'
}));
});
it('should clear cache and throw INTERNAL_SERVER_ERROR when update fails', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPathSettings.mockResolvedValue({
id: 5,
timestamp: 500,
lastSync: 1
});
syncQueries.updatePathSettings.mockRejectedValue(new Error('db'));
await expect(service.updatePath({
id: 1
}, 'c1', 5, {})).rejects.toMatchObject({
status: _common.HttpStatus.INTERNAL_SERVER_ERROR,
message: 'Unable to update path'
});
expect(syncQueries.clearCachePathSettings).toHaveBeenCalledWith('c1', 5);
});
});
describe('updatePaths', ()=>{
beforeEach(()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
spacesManager.spaceEnv.mockResolvedValue({
envPermissions: 'p'
});
});
it('should throw when client id is missing', async ()=>{
await expect(service.updatePaths(userWith(undefined), [])).rejects.toMatchObject({
status: _common.HttpStatus.BAD_REQUEST,
message: 'Client id is missing'
});
});
it('should throw FORBIDDEN when client does not belong to owner', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(false);
await expect(service.updatePaths(userWith('c1'), [])).rejects.toMatchObject({
status: _common.HttpStatus.FORBIDDEN
});
});
it('should mark client paths as deleted and notify when no corresponding server paths (server remotePath undefined)', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPaths.mockResolvedValue([
{
id: 2,
settings: {
timestamp: 1,
lastSync: 1
},
remotePath: undefined
}
]);
const clientPaths = [
{
id: 1,
remotePath: 'SPACES/a',
timestamp: 1,
lastSync: 1,
permissions: 'p'
}
];
const res = await service.updatePaths(userWith('c1'), clientPaths);
expect(res.delete).toEqual([
1
]);
expect(notificationsManager.create).toHaveBeenCalledTimes(1);
});
it('should propagate spaceEnv errors as BAD_REQUEST', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPaths.mockResolvedValue([
{
id: 1,
settings: {
timestamp: 1,
lastSync: 1
},
remotePath: 'SPACES/x'
}
]);
spacesManager.spaceEnv.mockRejectedValue(new Error('boom'));
await expect(service.updatePaths(userWith('c1'), [
{
id: 1,
remotePath: 'SPACES/x',
timestamp: 1,
lastSync: 1,
permissions: 'p'
}
])).rejects.toMatchObject({
status: _common.HttpStatus.BAD_REQUEST,
message: 'boom'
});
});
it('should skip server path when space is null and mark client item as deleted', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPaths.mockResolvedValue([
{
id: 2,
settings: {
timestamp: 1,
lastSync: 1
},
remotePath: 'SPACES/x'
}
]);
spacesManager.spaceEnv.mockResolvedValue(null);
const res = await service.updatePaths(userWith('c1'), [
{
id: 2,
remotePath: 'SPACES/x',
timestamp: 1,
lastSync: 1,
permissions: 'p'
}
]);
expect(res.delete).toEqual([
2
]);
expect(notificationsManager.create).toHaveBeenCalledTimes(1);
});
it('should add server-only path to client with server permissions', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPaths.mockResolvedValue([
{
id: 3,
settings: {
timestamp: 1,
lastSync: 1
},
remotePath: 'SPACES/x'
}
]);
spacesManager.spaceEnv.mockResolvedValue({
envPermissions: 'perm-xyz'
});
const res = await service.updatePaths(userWith('c1'), []);
expect(res.add).toHaveLength(1);
expect(res.add[0]).toEqual(expect.objectContaining({
id: 3,
remotePath: 'SPACES/x',
permissions: 'perm-xyz'
}));
});
it('should update server settings from client when client is newer (no hasUpdates)', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPaths.mockResolvedValue([
{
id: 5,
settings: {
timestamp: 1,
lastSync: 1,
foo: 'server'
},
remotePath: 'SPACES/x'
}
]);
spacesManager.spaceEnv.mockResolvedValue({
envPermissions: 'p'
});
const client = [
{
id: 5,
timestamp: 10,
lastSync: 2,
remotePath: 'SPACES/x',
permissions: 'p',
foo: 'client'
}
];
await service.updatePaths(userWith('c1'), client);
expect(syncQueries.updatePathSettings).toHaveBeenCalledWith('c1', 5, expect.objectContaining({
foo: 'client',
lastSync: 2
}));
// No client update instructions because hasUpdates=false and serverNewer=false
});
it('should push server-newer updates to client and also remotePath/permissions corrections', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPaths.mockResolvedValue([
{
id: 7,
settings: {
timestamp: 20,
lastSync: 5,
srv: true
},
remotePath: 'SPACES/correct'
}
]);
spacesManager.spaceEnv.mockResolvedValue({
envPermissions: 'permX'
});
const client = [
{
id: 7,
timestamp: 10,
lastSync: 5,
remotePath: 'SPACES/wrong',
permissions: 'old'
}
];
const res = await service.updatePaths(userWith('c1'), client);
// Should have two client updates: one full server settings + corrections, and one corrections-only
expect(res.update).toEqual(expect.arrayContaining([
expect.objectContaining({
id: 7,
srv: true,
remotePath: 'SPACES/correct',
permissions: 'permX'
}),
expect.objectContaining({
id: 7,
remotePath: 'SPACES/correct',
permissions: 'permX'
})
]));
expect(syncQueries.updatePathSettings).toHaveBeenCalledWith('c1', 7, expect.objectContaining({
lastSync: 5
}));
// clear cache called for each update instruction
expect(syncQueries.clearCachePathSettings).toHaveBeenCalledTimes(res.update.length);
for (const u of res.update){
expect(syncQueries.clearCachePathSettings).toHaveBeenCalledWith('c1', u.id);
}
});
it('should trigger update when lastSync differs even if timestamps and settings match', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPaths.mockResolvedValue([
{
id: 9,
settings: {
timestamp: 10,
lastSync: 1,
flag: 'S'
},
remotePath: 'SPACES/x'
}
]);
spacesManager.spaceEnv.mockResolvedValue({
envPermissions: 'p'
});
const client = [
{
id: 9,
timestamp: 10,
lastSync: 2,
remotePath: 'SPACES/x',
permissions: 'p',
flag: 'S'
}
];
await service.updatePaths(userWith('c1'), client);
expect(syncQueries.updatePathSettings).toHaveBeenCalledWith('c1', 9, expect.objectContaining({
lastSync: 2
}));
});
it('should perform no changes when client and server are identical (no-op branch)', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPaths.mockResolvedValue([
{
id: 12,
settings: {
timestamp: 5,
lastSync: 7,
flag: 'A'
},
remotePath: 'SPACES/x'
}
]);
spacesManager.spaceEnv.mockResolvedValue({
envPermissions: 'p'
});
const client = [
{
id: 12,
timestamp: 5,
lastSync: 7,
remotePath: 'SPACES/x',
permissions: 'p',
flag: 'A'
}
];
const res = await service.updatePaths(userWith('c1'), client);
expect(res).toEqual({
add: [],
update: [],
delete: []
});
expect(syncQueries.updatePathSettings).not.toHaveBeenCalled();
});
it('should correct client info while keeping server settings when timestamps are equal (hasUpdates=true)', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
// Server has srv='S', client has wrong remotePath/permissions and extra clientOnly flag
syncQueries.getPaths.mockResolvedValue([
{
id: 13,
settings: {
timestamp: 5,
lastSync: 1,
srv: 'S'
},
remotePath: 'SPACES/correct'
}
]);
spacesManager.spaceEnv.mockResolvedValue({
envPermissions: 'permX'
});
const client = [
{
id: 13,
timestamp: 5,
lastSync: 1,
remotePath: 'SPACES/wrong',
permissions: 'old',
clientOnly: 'C'
}
];
const res = await service.updatePaths(userWith('c1'), client);
// Server uses its own settings base (srv: 'S') with corrections applied
expect(syncQueries.updatePathSettings).toHaveBeenCalledWith('c1', 13, expect.objectContaining({
srv: 'S',
lastSync: 1,
remotePath: 'SPACES/correct',
permissions: 'permX'
}));
// Client should receive corrections for remotePath and permissions
expect(res.update).toEqual(expect.arrayContaining([
expect.objectContaining({
id: 13,
remotePath: 'SPACES/correct',
permissions: 'permX'
})
]));
});
it('should log error when updatePathSettings rejects inside updatePaths', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPaths.mockResolvedValue([
{
id: 11,
settings: {
timestamp: 1,
lastSync: 1
},
remotePath: 'SPACES/x'
}
]);
spacesManager.spaceEnv.mockResolvedValue({
envPermissions: 'p'
});
// Force client newer to trigger updatePathSettings
const client = [
{
id: 11,
timestamp: 10,
lastSync: 2,
remotePath: 'SPACES/x',
permissions: 'p'
}
];
syncQueries.updatePathSettings.mockRejectedValueOnce(new Error('db-fail'));
const loggerSpy = jest.spyOn(service.logger, 'error').mockImplementation(()=>undefined);
await service.updatePaths(userWith('c1'), client);
// wait for microtasks to ensure .catch executed
await flush();
expect(loggerSpy).toHaveBeenCalled();
expect(loggerSpy.mock.calls.some(([msg])=>String(msg).includes('updatePaths'))).toBe(true);
});
it('should catch notify failure at updatePaths level when building notification fails', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
// No server paths: will mark client path as deleted and call notify
syncQueries.getPaths.mockResolvedValue([]);
const loggerSpy = jest.spyOn(service.logger, 'error').mockImplementation(()=>undefined);
// BAD first segment to make notify fail while building URL (before notificationsManager.create)
const client = [
{
id: 1,
remotePath: 'BAD/a/b',
timestamp: 1,
lastSync: 1,
permissions: 'p'
}
];
const res = await service.updatePaths(userWith('c1'), client);
await flush();
expect(res.delete).toEqual([
1
]);
expect(loggerSpy).toHaveBeenCalled();
expect(loggerSpy.mock.calls.some(([msg])=>String(msg).includes('updatePaths'))).toBe(true);
// create not called because we failed before reaching it
expect(notificationsManager.create).not.toHaveBeenCalled();
});
it('should catch error inside notify when notifications creation fails', async ()=>{
syncQueries.clientExistsForOwner.mockResolvedValue(true);
syncQueries.getPaths.mockResolvedValue([]);
// Valid remotePath to build URL correctly
const client = [
{
id: 2,
remotePath: 'SPACES/a',
timestamp: 1,
lastSync: 1,
permissions: 'p'
}
];
const loggerSpy = jest.spyOn(service.logger, 'error').mockImplementation(()=>undefined);
notificationsManager.create.mockRejectedValueOnce(new Error('notify-fail'));
const res = await service.updatePaths(userWith('c1'), client);
await flush();
expect(res.delete).toEqual([
2
]);
expect(loggerSpy).toHaveBeenCalled();
// error comes from notify() catch
expect(loggerSpy.mock.calls.some(([msg])=>String(msg).includes('notify'))).toBe(true);
});
});
describe('getDBProps (private) branches', ()=>{
it('should throw BAD_REQUEST for shares list selection', async ()=>{
await expect(service.getDBProps({
inSharesList: true
})).rejects.toMatchObject({
status: _common.HttpStatus.BAD_REQUEST,
message: 'Sync all shares is not supported, you must select a sub-directory'
});
});
it('should return ownerId only for personal space at root', async ()=>{
const res = await service.getDBProps({
inSharesList: false,
inPersonalSpace: true,
paths: [],
dbFile: {
ownerId: 42
}
});
expect(res).toEqual({
ownerId: 42
});
});
it('should return ownerId and fileId for personal space subdir', async ()=>{
const getOrCreateFileIdSpy = jest.spyOn(service, 'getOrCreateFileId').mockResolvedValue(77);
const res = await service.getDBProps({
inPersonalSpace: true,
paths: [
'sub'
],
dbFile: {
ownerId: 42
}
});
expect(res).toEqual({
ownerId: 42,
fileId: 77
});
getOrCreateFileIdSpy.mockRestore();
});
it('should throw BAD_REQUEST for whole files repository without alias', async ()=>{
await expect(service.getDBProps({
inFilesRepository: true,
root: {
alias: null
},
paths: []
})).rejects.toMatchObject({
status: _common.HttpStatus.BAD_REQUEST,
message: 'Sync all space is not yet supported, you must select a sub-directory'
});
});
it('should return spaceId and rootId for files repository root selection', async ()=>{
const res = await service.getDBProps({
inFilesRepository: true,
id: 5,
root: {
id: 3,
alias: 'x'
},
paths: []
});
expect(res).toEqual({
spaceId: 5,
spaceRootId: 3
});
});
it('should return spaceId, rootId and fileId for files repository subdir or null root', async ()=>{
const getOrCreateFileIdSpy = jest.spyOn(service, 'getOrCreateFileId').mockResolvedValue(88);
const res = await service.getDBProps({
inFilesRepository: true,
id: 5,
root: {
id: 3,
alias: 'x'
},
paths: [
'sub'
]
});
expect(res).toEqual({
spaceId: 5,
spaceRootId: 3,
fileId: 88
});
getOrCreateFileIdSpy.mockRestore();
});
it('should return spaceId and null spaceRootId plus fileId when files repository root has no id', async ()=>{
const getOrCreateFileIdSpy = jest.spyOn(service, 'getOrCreateFileId').mockResolvedValue(90);
const res = await service.getDBProps({
inFilesRepository: true,
id: 6,
root: {
id: undefined,
alias: 'x'
},
paths: []
});
expect(res).toEqual({
spaceId: 6,
spaceRootId: null,
fileId: 90
});
getOrCreateFileIdSpy.mockRestore();
});
it('should return shareId only for shares repository root', async ()=>{
const res = await service.getDBProps({
inSharesList: false,
inPersonalSpace: false,
inFilesRepository: false,
inSharesRepository: true,
id: 9,
paths: []
});
expect(res).toEqual({
shareId: 9
});
});
it('should return shareId and fileId for shares repository subdir', async ()=>{
const getOrCreateFileIdSpy = jest.spyOn(service, 'getOrCreateFileId').mockResolvedValue(55);
const res = await service.getDBProps({
inSharesList: false,
inPersonalSpace: false,
inFilesRepository: false,
inSharesRepository: true,
id: 9,
paths: [
'sub'
]
});
expect(res).toEqual({
shareId: 9,
fileId: 55
});
getOrCreateFileIdSpy.mockRestore();
});
it('should return undefined when no space flags match (no branch taken)', async ()=>{
const res = await service.getDBProps({
inSharesList: false,
inPersonalSpace: false,
inFilesRepository: false,
inSharesRepository: false,
paths: []
});
expect(res).toBeUndefined();
});
});
describe('getOrCreateFileId (private) branches', ()=>{
it('should return existing file id without creation', async ()=>{
;
_files.getProps.mockResolvedValue({
name: 'file'
});
filesQueries.getSpaceFileId.mockResolvedValue(101);
const id = await service.getOrCreateFileId({
realPath: '/rp',
dbFile: {
path: '.'
}
});
expect(id).toBe(101);
expect(filesQueries.getOrCreateSpaceFile).not.toHaveBeenCalled();
});
it('should create file when not exists and return its id', async ()=>{
;
_files.getProps.mockResolvedValue({
id: 999,
name: 'file'
});
filesQueries.getSpaceFileId.mockResolvedValue(0);
filesQueries.getOrCreateSpaceFile.mockResolvedValue(202);
const id = await service.getOrCreateFileId({
realPath: '/rp',
dbFile: {
path: '.'
}
});
expect(id).toBe(202);
expect(filesQueries.getOrCreateSpaceFile).toHaveBeenCalledWith(0, expect.objectContaining({
id: undefined
}), {
path: '.'
});
});
});
});
//# sourceMappingURL=sync-paths-manager.service.spec.js.map