UNPKG

@sync-in/server

Version:

The secure, open-source platform for file storage, sharing, collaboration, and sync

949 lines (948 loc) 36.9 kB
/* * 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