@sync-in/server
Version:
The secure, open-source platform for file storage, sharing, collaboration, and sync
873 lines (872 loc) • 34.1 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 _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