UNPKG

@sync-in/server

Version:

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

873 lines (872 loc) 34.1 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 _promises = /*#__PURE__*/ _interop_require_default(require("node:fs/promises")); const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path")); const _operations = require("../../files/constants/operations"); const _fileerror = require("../../files/models/file-error"); const _filelockerror = require("../../files/models/file-lock-error"); const _filesmanagerservice = require("../../files/services/files-manager.service"); const _files = require("../../files/utils/files"); const _spacesmanagerservice = require("../../spaces/services/spaces-manager.service"); const _sync = require("../constants/sync"); const _syncmanagerservice = require("./sync-manager.service"); const _syncqueriesservice = require("./sync-queries.service"); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // Mock fs/promises used internally by the service jest.mock('fs/promises', ()=>({ __esModule: true, default: { stat: jest.fn(), readdir: jest.fn() } })); // Mock helper functions used in service jest.mock('../../files/utils/files', ()=>({ __esModule: true, checksumFile: jest.fn(), isPathExists: jest.fn(), isPathIsDir: jest.fn(), removeFiles: jest.fn(), touchFile: jest.fn(), sanitizePath: jest.fn((p)=>p) })); // Mock regExpPathPattern to a simple, predictable behavior jest.mock('../../../common/functions', ()=>({ __esModule: true, regExpPathPattern: (base)=>new RegExp('^' + base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) })); // Mock routes helper used by copyMove to bypass repo validation jest.mock('../utils/routes', ()=>({ __esModule: true, SYNC_PATH_TO_SPACE_SEGMENTS: jest.fn((dst)=>dst) })); // Mock heavy providers to avoid configuration side-effects on import jest.mock('../../files/services/files-manager.service', ()=>({ __esModule: true, FilesManager: class FilesManager { } })); jest.mock('../../spaces/services/spaces-manager.service', ()=>({ __esModule: true, SpacesManager: class SpacesManager { } })); jest.mock('./sync-queries.service', ()=>({ __esModule: true, SyncQueries: class SyncQueries { } })); const fsPromises = _promises.default; // small helper to collect async generators const collect = async (iter)=>{ const out = []; for await (const i of iter)out.push(i); return out; }; describe(_syncmanagerservice.SyncManager.name, ()=>{ let service; let filesManager; let spacesManager; let syncQueries; beforeAll(async ()=>{ filesManager = { sendFileFromSpace: jest.fn(), saveStream: jest.fn(), delete: jest.fn(), touch: jest.fn(), mkDir: jest.fn(), mkFile: jest.fn(), copyMove: jest.fn() }; spacesManager = { spaceEnv: jest.fn() }; syncQueries = { getPathSettings: jest.fn() }; const module = await _testing.Test.createTestingModule({ providers: [ _syncmanagerservice.SyncManager, { provide: _spacesmanagerservice.SpacesManager, useValue: spacesManager }, { provide: _filesmanagerservice.FilesManager, useValue: filesManager }, { provide: _syncqueriesservice.SyncQueries, useValue: syncQueries } ] }).compile(); module.useLogger([ 'fatal' ]); service = module.get(_syncmanagerservice.SyncManager); }); beforeEach(()=>jest.clearAllMocks()); const makeReq = (over)=>({ method: 'PUT', user: { id: 1, clientId: 42 }, space: { realPath: '/base/file.txt', url: '/space/file.txt' }, ...over }); const makeReply = ()=>{ const raw = { writeHead: jest.fn(), write: jest.fn(), end: jest.fn() }; return { raw, status: jest.fn().mockReturnThis() }; }; describe('download', ()=>{ it('should stream file successfully', async ()=>{ const req = makeReq(); const res = makeReply(); const checks = jest.fn().mockResolvedValue(undefined); const stream = jest.fn().mockResolvedValue(new _common.StreamableFile(Buffer.from('abc'))); filesManager.sendFileFromSpace.mockReturnValue({ checks, stream }); const result = await service.download(req, res); expect(filesManager.sendFileFromSpace).toHaveBeenCalledWith(req.space); expect(checks).toHaveBeenCalled(); expect(stream).toHaveBeenCalledWith(req, res); expect(result).toBeInstanceOf(_common.StreamableFile); }); it.each([ [ 'LockConflict maps to 423', new _filelockerror.LockConflict(null, 'locked'), _common.HttpStatus.LOCKED ], [ 'FileError maps to its http code', new _fileerror.FileError(_common.HttpStatus.BAD_REQUEST, 'bad'), _common.HttpStatus.BAD_REQUEST ], [ 'generic Error maps to 500', new Error('oops'), _common.HttpStatus.INTERNAL_SERVER_ERROR ] ])('should map errors (%s)', async (_title, thrown, expectedStatus)=>{ const req = makeReq(); const res = makeReply(); const checks = jest.fn().mockRejectedValue(thrown); const stream = jest.fn(); filesManager.sendFileFromSpace.mockReturnValue({ checks, stream }); await expect(service.download(req, res)).rejects.toMatchObject({ status: expectedStatus }); }); }); describe('upload', ()=>{ it('should upload with checksum OK and return ino', async ()=>{ const req = makeReq({ space: { realPath: '/tmp/up.bin', url: '/space/up.bin' } }); const dto = { checksum: 'abc', size: 10, mtime: 1710000000 }; filesManager.saveStream.mockResolvedValue('abc'); fsPromises.stat.mockResolvedValue({ size: 10, ino: 123, mtime: new Date(1710000000 * 1000) }); _files.touchFile.mockResolvedValue(undefined); const r = await service.upload(req, dto); expect(filesManager.saveStream).toHaveBeenCalledWith(req.user, req.space, req, { tmpPath: expect.any(String), checksumAlg: _sync.SYNC_CHECKSUM_ALG }); expect(_files.touchFile).toHaveBeenCalledWith('/tmp/up.bin', 1710000000); expect(r).toEqual({ ino: 123 }); expect(_files.removeFiles).not.toHaveBeenCalled(); }); it('should reject when checksum mismatches and remove tmp', async ()=>{ const req = makeReq(); const dto = { checksum: 'abc', size: 10, mtime: 1710000000 }; filesManager.saveStream.mockResolvedValue('bad'); fsPromises.stat.mockResolvedValue({ size: 10, ino: 123, mtime: new Date(1710000000 * 1000) }); await expect(service.upload(req, dto)).rejects.toBeInstanceOf(_common.HttpException); expect(_files.removeFiles).toHaveBeenCalledWith(expect.any(String)); }); it('should upload without checksum', async ()=>{ const req = makeReq({ space: { realPath: '/tmp/up2.bin', url: '/space/up2.bin' } }); const dto = { size: 5, mtime: 1710000100 }; filesManager.saveStream.mockResolvedValue(undefined); fsPromises.stat.mockResolvedValue({ size: 5, ino: 321, mtime: new Date(1710000100 * 1000) }); const r = await service.upload(req, dto); expect(filesManager.saveStream).toHaveBeenCalledWith(req.user, req.space, req, { tmpPath: expect.any(String) }); expect(_files.touchFile).toHaveBeenCalledWith('/tmp/up2.bin', 1710000100); expect(r).toEqual({ ino: 321 }); }); it('should reject when size mismatches and remove tmp', async ()=>{ const req = makeReq(); const dto = { size: 10, mtime: 1710000100 }; filesManager.saveStream.mockResolvedValue(undefined); fsPromises.stat.mockResolvedValue({ size: 99, ino: 321, mtime: new Date(1710000100 * 1000) }); await expect(service.upload(req, dto)).rejects.toBeInstanceOf(_common.HttpException); expect(_files.removeFiles).toHaveBeenCalledWith(expect.any(String)); }); }); describe('delete', ()=>{ it('should delete successfully', async ()=>{ const req = makeReq(); filesManager.delete.mockResolvedValue(undefined); await expect(service.delete(req)).resolves.toBeUndefined(); expect(filesManager.delete).toHaveBeenCalledWith(req.user, req.space); }); it('should map errors via handleError', async ()=>{ const req = makeReq(); filesManager.delete.mockRejectedValue(new _filelockerror.LockConflict(null, 'locked')); await expect(service.delete(req)).rejects.toMatchObject({ status: _common.HttpStatus.LOCKED }); }); }); describe('props', ()=>{ it('should touch successfully', async ()=>{ const req = makeReq(); filesManager.touch.mockResolvedValue(undefined); await expect(service.props(req, { mtime: 1710000200 })).resolves.toBeUndefined(); expect(filesManager.touch).toHaveBeenCalledWith(req.user, req.space, 1710000200, false); }); it('should map errors via handleError', async ()=>{ const req = makeReq(); filesManager.touch.mockRejectedValue(new _fileerror.FileError(_common.HttpStatus.BAD_REQUEST, 'bad')); await expect(service.props(req, { mtime: 123 })).rejects.toMatchObject({ status: _common.HttpStatus.BAD_REQUEST }); }); }); describe('make', ()=>{ it('should create directory and return ino', async ()=>{ const req = makeReq({ space: { realPath: '/tmp/newdir', url: '/space/newdir' } }); filesManager.mkDir.mockResolvedValue(undefined); fsPromises.stat.mockResolvedValue({ ino: 555 }); _files.touchFile.mockResolvedValue(undefined); const r = await service.make(req, { type: 'directory', mtime: 1710000300 }); expect(filesManager.mkDir).toHaveBeenCalledWith(req.user, req.space, true); expect(_files.touchFile).toHaveBeenCalledWith('/tmp/newdir', 1710000300); expect(r).toEqual({ ino: 555 }); }); it('should create file and return ino', async ()=>{ const req = makeReq({ space: { realPath: '/tmp/newfile', url: '/space/newfile' } }); filesManager.mkFile.mockResolvedValue(undefined); fsPromises.stat.mockResolvedValue({ ino: 777 }); const r = await service.make(req, { type: 'file', mtime: 1710000400 }); expect(filesManager.mkFile).toHaveBeenCalledWith(req.user, req.space, true); expect(_files.touchFile).toHaveBeenCalledWith('/tmp/newfile', 1710000400); expect(r).toEqual({ ino: 777 }); }); it('should map errors via handleError', async ()=>{ const req = makeReq(); filesManager.mkDir.mockRejectedValue(new _filelockerror.LockConflict(null, 'locked')); await expect(service.make(req, { type: 'directory', mtime: 0 })).rejects.toMatchObject({ status: _common.HttpStatus.LOCKED }); }); }); describe('copyMove', ()=>{ it('should move (no return) and not touch mtime', async ()=>{ const req = makeReq(); const dstSpace = { realPath: '/dst/moved', url: '/space/dst/moved' }; spacesManager.spaceEnv.mockResolvedValue(dstSpace); filesManager.copyMove.mockResolvedValue(undefined); const r = await service.copyMove(req, { destination: '/dst/moved' }, true); expect(spacesManager.spaceEnv).toHaveBeenCalled(); expect(filesManager.copyMove).toHaveBeenCalledWith(req.user, req.space, dstSpace, true, true, true); expect(_files.touchFile).not.toHaveBeenCalled(); expect(r).toBeUndefined(); }); it('should copy, touch mtime when provided, and return ino/mtime', async ()=>{ const req = makeReq(); const dstSpace = { realPath: '/dst/copied', url: '/space/dst/copied' }; spacesManager.spaceEnv.mockResolvedValue(dstSpace); filesManager.copyMove.mockResolvedValue(undefined); fsPromises.stat.mockResolvedValue({ ino: 999, mtime: new Date(1710000500 * 1000) }); const r = await service.copyMove(req, { destination: '/dst/copied', mtime: 1710000500 }, false); expect(filesManager.copyMove).toHaveBeenCalledWith(req.user, req.space, dstSpace, false, true, true); expect(_files.touchFile).toHaveBeenCalledWith('/dst/copied', 1710000500); expect(r).toEqual({ ino: 999, mtime: 1710000500 }); }); it('should copy without mtime and still return ino/mtime', async ()=>{ const req = makeReq(); const dstSpace = { realPath: '/dst/copied2', url: '/space/dst/copied2' }; spacesManager.spaceEnv.mockResolvedValue(dstSpace); filesManager.copyMove.mockResolvedValue(undefined); fsPromises.stat.mockResolvedValue({ ino: 1001, mtime: new Date(1710000600 * 1000) }); const r = await service.copyMove(req, { destination: '/dst/copied2' }, false); expect(_files.touchFile).not.toHaveBeenCalled(); expect(r).toEqual({ ino: 1001, mtime: 1710000600 }); }); it('should map errors via handleError', async ()=>{ const req = makeReq(); const dstSpace = { realPath: '/dst/err', url: '/space/dst/err' }; spacesManager.spaceEnv.mockResolvedValue(dstSpace); filesManager.copyMove.mockRejectedValue(new _fileerror.FileError(_common.HttpStatus.BAD_REQUEST, 'bad')); await expect(service.copyMove(req, { destination: '/dst/err' }, false)).rejects.toMatchObject({ status: _common.HttpStatus.BAD_REQUEST }); }); it('should map errors via handleError for move (isMove=true)', async ()=>{ const req = makeReq(); const dstSpace = { realPath: '/dst/err-move', url: '/space/dst/err-move' }; spacesManager.spaceEnv.mockResolvedValue(dstSpace); filesManager.copyMove.mockRejectedValue(new _filelockerror.LockConflict(null, 'locked')); const spy = jest.spyOn(service, 'handleError'); await expect(service.copyMove(req, { destination: '/dst/err-move' }, true)).rejects.toMatchObject({ status: _common.HttpStatus.LOCKED }); expect(spy).toHaveBeenCalledWith(req.space, _operations.FILE_OPERATION.MOVE, expect.anything(), dstSpace); }); }); describe('parseSyncPath', ()=>{ it('should delegate to parseFiles and yield file stats from the base directory', async ()=>{ const base = '/base-sync'; const space = { realPath: base, url: '/space-sync', quotaIsExceeded: false }; const dirent = { name: 'afile', parentPath: base, isDirectory: ()=>false, isFile: ()=>true }; fsPromises.readdir.mockResolvedValue([ dirent ]); fsPromises.stat.mockResolvedValue({ isDirectory: ()=>false, isFile: ()=>true, size: 42, ino: 7, mtime: new Date(1234 * 1000) }); const syncDiff = { defaultFilters: new Set(), secureDiff: false, firstSync: true, snapshot: new Map() }; const out = await collect(service.parseSyncPath(space, syncDiff)); expect(out.length).toBe(1); expect(out[0]).toHaveProperty('/afile'); const stats = out[0]['/afile']; expect(Array.isArray(stats)).toBe(true); expect(stats[_sync.F_STAT.IS_DIR]).toBe(false); expect(stats[_sync.F_STAT.SIZE]).toBe(42); expect(stats[_sync.F_STAT.MTIME]).toBe(1234); expect(stats[_sync.F_STAT.INO]).toBe(7); expect(stats[_sync.F_STAT.CHECKSUM]).toBeNull(); }); }); describe('diff', ()=>{ it('should fail when clientId is missing', async ()=>{ const res = makeReply(); await expect(service.diff({ id: 1 }, 1, {}, res)).rejects.toMatchObject({ status: _common.HttpStatus.BAD_REQUEST }); }); it('should fail when path settings not found', async ()=>{ const res = makeReply(); const user = { id: 1, clientId: 9 }; syncQueries.getPathSettings.mockResolvedValue(undefined); await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({ status: _common.HttpStatus.NOT_FOUND }); }); it('should map spaceEnv thrown error to BAD_REQUEST', async ()=>{ const res = makeReply(); const user = { id: 1, clientId: 9 }; syncQueries.getPathSettings.mockResolvedValue({ remotePath: '/base' }); spacesManager.spaceEnv.mockRejectedValue(new Error('boom')); await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({ status: _common.HttpStatus.BAD_REQUEST, message: 'boom' }); }); it('should fail when space not found', async ()=>{ const res = makeReply(); const user = { id: 1, clientId: 9 }; syncQueries.getPathSettings.mockResolvedValue({ remotePath: '/base' }); spacesManager.spaceEnv.mockResolvedValue(undefined); await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({ status: _common.HttpStatus.NOT_FOUND }); }); it('should fail when space quota exceeded', async ()=>{ const res = makeReply(); const user = { id: 1, clientId: 9 }; syncQueries.getPathSettings.mockResolvedValue({ remotePath: '/base' }); spacesManager.spaceEnv.mockResolvedValue({ realPath: '/base', url: '/space', quotaIsExceeded: true }); await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({ status: _common.HttpStatus.INSUFFICIENT_STORAGE }); }); it('should fail when remote path does not exist', async ()=>{ const res = makeReply(); const user = { id: 1, clientId: 9 }; syncQueries.getPathSettings.mockResolvedValue({ remotePath: '/base' }); spacesManager.spaceEnv.mockResolvedValue({ realPath: '/base', url: '/space', quotaIsExceeded: false }); _files.isPathExists.mockResolvedValue(false); await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({ status: _common.HttpStatus.NOT_FOUND, message: 'Remote path not found : /base' }); }); it('should fail when remote path is not a directory', async ()=>{ const res = makeReply(); const user = { id: 1, clientId: 9 }; syncQueries.getPathSettings.mockResolvedValue({ remotePath: '/base' }); spacesManager.spaceEnv.mockResolvedValue({ realPath: '/base', url: '/space', quotaIsExceeded: false }); _files.isPathExists.mockResolvedValue(true); _files.isPathIsDir.mockResolvedValue(false); await expect(service.diff(user, 1, {}, res)).rejects.toMatchObject({ status: _common.HttpStatus.BAD_REQUEST, message: 'Remote path must be a directory' }); }); it('should stream diff results successfully', async ()=>{ const res = makeReply(); const user = { id: 1, clientId: 9 }; syncQueries.getPathSettings.mockResolvedValue({ remotePath: '/base' }); const space = { realPath: '/base', url: '/space', quotaIsExceeded: false }; spacesManager.spaceEnv.mockResolvedValue(space); _files.isPathExists.mockResolvedValue(true); _files.isPathIsDir.mockResolvedValue(true); const gen = async function*() { yield { '/file1': [ false, 1, 2, 3, 'x' ] }; yield { '/file2': [ true, 0, 2, 4, null ] }; }; jest.spyOn(service, 'parseSyncPath').mockImplementation(()=>gen()); await service.diff(user, 1, { secureDiff: false }, res); expect(res.raw.writeHead).toHaveBeenCalledWith(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Transfer-Encoding': 'chunked' }); expect(res.raw.write).toHaveBeenCalledWith(`${JSON.stringify({ '/file1': [ false, 1, 2, 3, 'x' ] })}\n`); expect(res.raw.write).toHaveBeenCalledWith(`${JSON.stringify({ '/file2': [ true, 0, 2, 4, null ] })}\n`); expect(res.raw.write).toHaveBeenCalledWith(_sync.SYNC_DIFF_DONE); expect(res.raw.end).toHaveBeenCalled(); }); it('should handle error during streaming and set status 500', async ()=>{ const res = makeReply(); const user = { id: 1, clientId: 9 }; syncQueries.getPathSettings.mockResolvedValue({ remotePath: '/base' }); const space = { realPath: '/base', url: '/space', quotaIsExceeded: false }; spacesManager.spaceEnv.mockResolvedValue(space); _files.isPathExists.mockResolvedValue(true); _files.isPathIsDir.mockResolvedValue(true); jest.spyOn(service, 'parseSyncPath').mockImplementation(()=>{ throw new Error('parse error'); }); await service.diff(user, 1, {}, res); expect(res.raw.write).toHaveBeenCalledWith('parse error\n'); expect(res.status).toHaveBeenCalledWith(_common.HttpStatus.INTERNAL_SERVER_ERROR); expect(res.raw.end).toHaveBeenCalled(); }); }); describe('internal parseFiles/analyzeFile coverage', ()=>{ const makeDirent = (name, parentPath, kind)=>({ name, parentPath, isDirectory: ()=>kind === 'dir', isFile: ()=>kind === 'file' }); it('should walk directory, ignore special files, filter by name/path, reuse snapshot checksum, and compute checksum when needed', async ()=>{ const base = '/base'; const ctx = { regexBasePath: new RegExp('^/base'), syncDiff: { defaultFilters: new Set([ 'ignoredName' ]), pathFilters: /file2/, secureDiff: true, firstSync: false, snapshot: new Map([ [ '/file3', [ false, 100, 1000, 33, 'snaphash' ] ] ]) } }; fsPromises.readdir.mockImplementation(async (dir)=>{ if (dir === base) { return [ makeDirent('special', base, 'other'), makeDirent('dir1', base, 'dir'), makeDirent('ignoredName', base, 'file'), makeDirent('fileStatError', base, 'file'), makeDirent('file2', base, 'file'), makeDirent('file3', base, 'file'), makeDirent('file4', base, 'file') ]; } if (dir === _nodepath.default.join(base, 'dir1')) return []; return []; }); const mtimeDate = new Date(1000 * 1000); fsPromises.stat.mockImplementation(async (p)=>{ switch(p){ case _nodepath.default.join(base, 'dir1'): return { isDirectory: ()=>true, isFile: ()=>false, size: 0, ino: 11, mtime: mtimeDate }; case _nodepath.default.join(base, 'fileStatError'): throw new Error('stat fail'); case _nodepath.default.join(base, 'file2'): return { isDirectory: ()=>false, isFile: ()=>true, size: 10, ino: 22, mtime: mtimeDate }; case _nodepath.default.join(base, 'file3'): return { isDirectory: ()=>false, isFile: ()=>true, size: 100, ino: 33, mtime: new Date(1000 * 1000) }; case _nodepath.default.join(base, 'file4'): return { isDirectory: ()=>false, isFile: ()=>true, size: 200, ino: 44, mtime: new Date(2000 * 1000) }; default: return { isDirectory: ()=>false, isFile: ()=>false, size: 0, ino: 0, mtime: new Date() }; } }); _files.checksumFile.mockResolvedValue('computed-hash'); const results = await collect(service.parseFiles(base, ctx)); const keys = results.map((o)=>Object.keys(o)[0]).sort(); expect(keys).toEqual([ '/dir1', '/file2', '/file3', '/file4', '/fileStatError' ]); const fileStatError = results.find((o)=>o['/fileStatError']); expect(fileStatError?.['/fileStatError'][0]).toBe(_sync.F_SPECIAL_STAT.ERROR); expect(fileStatError?.['/fileStatError'][1]).toContain('stat fail'); const filtered = results.find((o)=>o['/file2']); expect(filtered?.['/file2'][0]).toBe(_sync.F_SPECIAL_STAT.FILTERED); const reused = results.find((o)=>o['/file3'])?.['/file3']; expect(reused[_sync.F_STAT.CHECKSUM]).toBe('snaphash'); expect(_files.checksumFile).toHaveBeenCalledTimes(1); const computed = results.find((o)=>o['/file4'])?.['/file4']; expect(computed[_sync.F_STAT.CHECKSUM]).toBe('computed-hash'); }); it('should throw a generic error when readdir fails', async ()=>{ fsPromises.readdir.mockRejectedValue(new Error('readdir fail')); const ctx = { regexBasePath: /./, syncDiff: { defaultFilters: new Set(), secureDiff: false } }; const iter = service.parseFiles('/any', ctx); await expect((async ()=>{ for await (const _ of iter){ /* consume */ } })()).rejects.toThrow('Unable to parse path'); }); it('should return ERROR when checkSumFile throws during analyzeFile', async ()=>{ const base = '/base'; const dirent = { name: 'badfile', parentPath: base, isDirectory: ()=>false, isFile: ()=>true }; fsPromises.readdir.mockImplementation(async (dir)=>dir === base ? [ dirent ] : []); fsPromises.stat.mockResolvedValue({ isDirectory: ()=>false, isFile: ()=>true, size: 10, ino: 42, mtime: new Date(1234 * 1000) }); jest.spyOn(service, 'checkSumFile').mockRejectedValue(new Error('checksum fail')); const ctx = { regexBasePath: new RegExp('^/base'), syncDiff: { defaultFilters: new Set(), pathFilters: undefined, secureDiff: true, firstSync: false, snapshot: new Map() } }; const results = await collect(service.parseFiles(base, ctx)); expect(results).toHaveLength(1); expect(results[0]).toHaveProperty('/badfile'); const out = results[0]['/badfile']; expect(Array.isArray(out)).toBe(true); expect(out[0]).toBe(_sync.F_SPECIAL_STAT.ERROR); expect(String(out[1])).toContain('checksum fail'); }); }); }); //# sourceMappingURL=sync-manager.service.spec.js.map