Files
cloudron-box/src/test/backups-test.js
T

452 lines
19 KiB
JavaScript
Raw Normal View History

2017-04-24 12:34:57 +02:00
/* jslint node:true */
/* global it:false */
/* global describe:false */
/* global before:false */
/* global after:false */
'use strict';
2021-06-03 12:20:44 -07:00
const async = require('async'),
2017-04-24 12:34:57 +02:00
backupdb = require('../backupdb.js'),
backups = require('../backups.js'),
2019-10-22 11:03:56 -07:00
BoxError = require('../boxerror.js'),
2021-06-03 12:20:44 -07:00
common = require('./common.js'),
2019-01-22 17:43:24 -08:00
DataLayout = require('../datalayout.js'),
2021-07-12 23:35:30 -07:00
delay = require('delay'),
2017-04-24 12:34:57 +02:00
expect = require('expect.js'),
2017-09-29 14:16:11 -07:00
fs = require('fs'),
os = require('os'),
2020-05-21 14:30:21 -07:00
moment = require('moment'),
2017-09-29 14:16:11 -07:00
path = require('path'),
rimraf = require('rimraf'),
2021-07-12 23:35:30 -07:00
safe = require('safetydance'),
2020-06-14 15:09:04 -07:00
settingsdb = require('../settingsdb.js'),
2017-10-02 20:08:00 -07:00
settings = require('../settings.js'),
2021-07-12 23:35:30 -07:00
tasks = require('../tasks.js'),
util = require('util');
2017-10-02 20:08:00 -07:00
2021-06-03 12:20:44 -07:00
const { createTree, APP } = common;
2017-10-02 20:08:00 -07:00
function createBackup(callback) {
backups.startBackupTask({ username: 'test' }, function (error, taskId) { // this call does not wait for the backup!
2017-10-02 20:08:00 -07:00
if (error) return callback(error);
2021-07-12 23:35:30 -07:00
async function waitForBackup() {
const [error, p] = await safe(tasks.get(taskId));
if (error) return callback(error);
2017-10-02 20:08:00 -07:00
2021-07-12 23:35:30 -07:00
if (p.percent !== 100) return setTimeout(waitForBackup, 1000);
2017-10-02 20:08:00 -07:00
2021-07-12 23:35:30 -07:00
if (p.errorMessage) return callback(new Error('backup failed:' + p));
if (!p.result) return callback(new Error('backup has no result:' + p));
2017-10-02 20:08:00 -07:00
2021-07-12 23:35:30 -07:00
backups.getByIdentifierAndStatePaged(backups.BACKUP_IDENTIFIER_BOX, backups.BACKUP_STATE_NORMAL, 1, 1, function (error, result) {
if (error) return callback(error);
if (result.length !== 1) return callback(new Error('result is not of length 1'));
2018-11-16 11:13:03 -08:00
2021-07-12 23:35:30 -07:00
// the task progress and the db entry is set in the worker. wait for 2 seconds for backup lock to get released in parent process
setTimeout(() => callback(null, result[0]), 2000);
2017-10-02 20:08:00 -07:00
});
}
setTimeout(waitForBackup, 1000);
});
}
2017-04-24 12:34:57 +02:00
2021-07-12 23:35:30 -07:00
async function cleanupBackups() {
const taskId = await backups.startCleanupTask({ username: 'test' });
2019-01-10 16:00:49 -08:00
2021-07-12 23:35:30 -07:00
// eslint-disable-next-line no-constant-condition
while (true) {
await delay(1000);
2019-01-10 16:00:49 -08:00
2021-07-12 23:35:30 -07:00
const p = await tasks.get(taskId);
2019-01-10 16:00:49 -08:00
2021-07-12 23:35:30 -07:00
if (p.percent !== 100) continue;
if (p.errorMessage) throw new Error('backup failed:' + p.errorMessage);
2019-01-10 16:00:49 -08:00
2021-07-12 23:35:30 -07:00
return;
}
2019-01-10 16:00:49 -08:00
}
describe('backups', function () {
2017-04-24 12:34:57 +02:00
before(function (done) {
2017-11-15 02:29:58 +01:00
const BACKUP_DIR = path.join(os.tmpdir(), 'cloudron-backup-test');
2017-04-24 12:34:57 +02:00
async.series([
2021-06-03 12:20:44 -07:00
common.setup,
2020-06-11 08:27:48 -07:00
fs.mkdir.bind(null, BACKUP_DIR, { recursive: true }),
2020-06-14 15:09:04 -07:00
settingsdb.set.bind(null, settings.BACKUP_CONFIG_KEY, JSON.stringify({
2017-04-24 12:34:57 +02:00
provider: 'filesystem',
2020-05-12 10:31:51 -07:00
password: 'supersecret',
2017-11-15 02:29:58 +01:00
backupFolder: BACKUP_DIR,
2020-05-14 20:05:27 -07:00
retentionPolicy: { keepWithinSecs: 1 },
2017-09-27 10:25:36 -07:00
format: 'tgz'
2020-06-14 15:09:04 -07:00
}))
2017-04-24 12:34:57 +02:00
], done);
});
2021-06-03 12:20:44 -07:00
after(common.cleanup);
describe('retention policy', function () {
it('keeps latest', function () {
let backup = { creationTime: moment().subtract(5, 's').toDate(), state: backups.BACKUP_STATE_NORMAL };
backups._applyBackupRetentionPolicy([backup], { keepWithinSecs: 1, keepLatest: true }, []);
expect(backup.keepReason).to.be('latest');
});
it('does not keep latest', function () {
let backup = { creationTime: moment().subtract(5, 's').toDate(), state: backups.BACKUP_STATE_NORMAL };
backups._applyBackupRetentionPolicy([backup], { keepWithinSecs: 1, keepLatest: false }, []);
expect(backup.keepReason).to.be(undefined);
});
it('always keeps forever policy', function () {
let backup = { creationTime: new Date() };
backups._applyBackupRetentionPolicy([backup], { keepWithinSecs: -1, keepLatest: true }, []);
expect(backup.keepReason).to.be('keepWithinSecs');
});
it('preserveSecs takes precedence', function () {
let backup = { creationTime: new Date(), preserveSecs: 3000 };
backups._applyBackupRetentionPolicy([backup], { keepWithinSecs: 1, keepLatest: true }, []);
expect(backup.keepReason).to.be('preserveSecs');
});
it('1 daily', function () {
let b = [
{ id: '0', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().toDate() },
{ id: '1', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(1, 'h').toDate() },
{ id: '2', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(3, 'h').toDate() },
{ id: '3', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(20, 'h').toDate() },
{ id: '4', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(5, 'd').toDate() }
];
backups._applyBackupRetentionPolicy(b, { keepDaily: 1, keepLatest: true }, []);
expect(b[0].keepReason).to.be('keepDaily');
expect(b[1].keepReason).to.be(undefined);
expect(b[2].keepReason).to.be(undefined);
expect(b[3].keepReason).to.be(undefined);
expect(b[3].keepReason).to.be(undefined);
});
// if you are debugging this test, it's because of some timezone issue with all the hour substraction!
it('2 daily, 1 weekly', function () {
let b = [
{ id: '0', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().toDate() },
{ id: '1', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(1, 'h').toDate() },
{ id: '2', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(3, 'h').toDate() },
{ id: '3', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(26, 'h').toDate() },
{ id: '4', state: backups.BACKUP_STATE_ERROR, creationTime: moment().subtract(32, 'h').toDate() },
{ id: '5', state: backups.BACKUP_STATE_CREATING, creationTime: moment().subtract(50, 'h').toDate() },
{ id: '6', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(5, 'd').toDate() }
];
backups._applyBackupRetentionPolicy(b, { keepDaily: 2, keepWeekly: 1, keepLatest: false }, []);
expect(b[0].keepReason).to.be('keepDaily'); // today
expect(b[1].keepReason).to.be('keepWeekly'); // today
expect(b[2].keepReason).to.be(undefined);
expect(b[3].keepReason).to.be('keepDaily'); // yesterday
expect(b[4].discardReason).to.be('error'); // errored
expect(b[5].discardReason).to.be('creating-too-long'); // creating for too long
expect(b[6].keepReason).to.be(undefined); // outside retention policy
});
it('2 daily, 3 monthly, 1 yearly', function () {
let b = [
{ id: '0', state: backups.BACKUP_STATE_CREATING, creationTime: moment().toDate() },
{ id: '1', state: backups.BACKUP_STATE_ERROR, creationTime: moment().toDate() },
{ id: '2', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().toDate() },
{ id: '3', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(1, 'h').toDate() },
{ id: '4', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(3, 'h').toDate() },
{ id: '5', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(26, 'h').toDate() },
{ id: '6', state: backups.BACKUP_STATE_CREATING, creationTime: moment().subtract(49, 'h').toDate() },
{ id: '7', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(51, 'd').toDate() },
{ id: '8', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(84, 'd').toDate() },
{ id: '9', state: backups.BACKUP_STATE_NORMAL, creationTime: moment().subtract(97, 'd').toDate() },
];
backups._applyBackupRetentionPolicy(b, { keepDaily: 2, keepMonthly: 3, keepYearly: 1, keepLatest: true }, []);
expect(b[0].keepReason).to.be('creating');
expect(b[1].discardReason).to.be('error'); // errored
expect(b[2].keepReason).to.be('keepDaily');
expect(b[3].keepReason).to.be('keepMonthly');
expect(b[4].keepReason).to.be('keepYearly');
expect(b[5].keepReason).to.be('keepDaily'); // yesterday
expect(b[6].discardReason).to.be('creating-too-long'); // errored
expect(b[7].keepReason).to.be('keepMonthly');
expect(b[8].keepReason).to.be('keepMonthly');
expect(b[9].keepReason).to.be(undefined);
});
2017-04-24 12:34:57 +02:00
});
describe('cleanup', function () {
var BACKUP_0 = {
id: 'backup-box-0',
2020-06-14 11:29:07 -07:00
identifier: 'box',
2020-05-13 22:09:33 -07:00
encryptionVersion: null,
2020-05-13 21:32:36 -07:00
packageVersion: '1.0.0',
2020-06-14 14:01:01 -07:00
type: backups.BACKUP_TYPE_BOX,
state: backups.BACKUP_STATE_NORMAL,
2017-04-24 12:34:57 +02:00
dependsOn: [ 'backup-app-00', 'backup-app-01' ],
manifest: null,
2017-09-27 17:34:49 -07:00
format: 'tgz'
2017-04-24 12:34:57 +02:00
};
2020-06-14 15:09:04 -07:00
var BACKUP_0_APP_0 = { // backup of installed app
2017-04-24 12:34:57 +02:00
id: 'backup-app-00',
2021-06-03 12:20:44 -07:00
identifier: APP.id,
2020-05-13 22:09:33 -07:00
encryptionVersion: null,
2020-05-13 21:32:36 -07:00
packageVersion: '1.0.0',
type: backups.BACKUP_TYPE_APP,
2020-06-14 14:01:01 -07:00
state: backups.BACKUP_STATE_NORMAL,
2017-04-24 12:34:57 +02:00
dependsOn: [],
manifest: null,
2017-09-27 17:34:49 -07:00
format: 'tgz'
2017-04-24 12:34:57 +02:00
};
2020-06-14 15:09:04 -07:00
var BACKUP_0_APP_1 = { // this app is uninstalled
2017-04-24 12:34:57 +02:00
id: 'backup-app-01',
2020-06-14 11:29:07 -07:00
identifier: 'app1',
2020-05-13 22:09:33 -07:00
encryptionVersion: null,
2020-05-13 21:32:36 -07:00
packageVersion: '1.0.0',
type: backups.BACKUP_TYPE_APP,
2020-06-14 14:01:01 -07:00
state: backups.BACKUP_STATE_NORMAL,
2017-04-24 12:34:57 +02:00
dependsOn: [],
manifest: null,
2017-09-27 17:34:49 -07:00
format: 'tgz'
2017-04-24 12:34:57 +02:00
};
var BACKUP_1 = {
id: 'backup-box-1',
2020-05-13 22:09:33 -07:00
encryptionVersion: null,
2020-05-13 21:32:36 -07:00
packageVersion: '1.0.0',
type: backups.BACKUP_TYPE_BOX,
2020-06-14 14:01:01 -07:00
state: backups.BACKUP_STATE_NORMAL,
2020-06-14 11:29:07 -07:00
identifier: 'box',
2017-04-24 12:34:57 +02:00
dependsOn: [ 'backup-app-10', 'backup-app-11' ],
manifest: null,
2017-09-27 17:34:49 -07:00
format: 'tgz'
2017-04-24 12:34:57 +02:00
};
var BACKUP_1_APP_0 = {
id: 'backup-app-10',
2020-05-13 22:09:33 -07:00
encryptionVersion: null,
2020-05-13 21:32:36 -07:00
packageVersion: '1.0.0',
type: backups.BACKUP_TYPE_APP,
2020-06-14 14:01:01 -07:00
state: backups.BACKUP_STATE_NORMAL,
2021-06-03 12:20:44 -07:00
identifier: APP.id,
2017-04-24 12:34:57 +02:00
dependsOn: [],
manifest: null,
2017-09-27 17:34:49 -07:00
format: 'tgz'
2017-04-24 12:34:57 +02:00
};
var BACKUP_1_APP_1 = {
id: 'backup-app-11',
2020-05-13 22:09:33 -07:00
encryptionVersion: null,
2020-05-13 21:32:36 -07:00
packageVersion: '1.0.0',
type: backups.BACKUP_TYPE_APP,
2020-06-14 14:01:01 -07:00
state: backups.BACKUP_STATE_NORMAL,
2020-06-14 11:29:07 -07:00
identifier: 'app1',
2017-04-24 12:34:57 +02:00
dependsOn: [],
manifest: null,
2017-09-27 17:34:49 -07:00
format: 'tgz'
2017-04-24 12:34:57 +02:00
};
2021-07-12 23:35:30 -07:00
it('succeeds without backups', async function () {
cleanupBackups();
2017-04-24 12:34:57 +02:00
});
it('succeeds with box backups, keeps latest', function (done) {
async.eachSeries([[ BACKUP_0, BACKUP_0_APP_0, BACKUP_0_APP_1 ], [ BACKUP_1, BACKUP_1_APP_0, BACKUP_1_APP_1 ]], function (backup, callback) {
// space out backups
setTimeout(function () {
2021-06-03 12:20:44 -07:00
async.eachSeries(backup, (b, done) => backupdb.add(b.id, b, done), callback);
2017-04-24 12:34:57 +02:00
}, 2000);
}, function (error) {
expect(error).to.not.be.ok();
2021-07-12 23:35:30 -07:00
util.callbackify(cleanupBackups)(function (error) {
2017-04-24 12:34:57 +02:00
expect(error).to.not.be.ok();
2020-06-14 14:01:01 -07:00
backupdb.getByTypePaged(backups.BACKUP_TYPE_BOX, 1, 1000, function (error, result) {
2017-04-24 12:34:57 +02:00
expect(error).to.not.be.ok();
expect(result.length).to.equal(1);
expect(result[0].id).to.equal(BACKUP_1.id);
2020-06-14 15:09:04 -07:00
// check that app backups are gone as well. only backup_1 will remain
2017-04-24 12:34:57 +02:00
backupdb.get(BACKUP_0_APP_0.id, function (error) {
expect(error).to.be.a(BoxError);
expect(error.reason).to.equal(BoxError.NOT_FOUND);
2017-04-24 12:34:57 +02:00
done();
});
});
});
});
});
it('does not remove expired backups if only one left', function (done) {
2021-07-12 23:35:30 -07:00
util.callbackify(cleanupBackups)(function (error) {
2017-04-24 12:34:57 +02:00
expect(error).to.not.be.ok();
2020-06-14 14:01:01 -07:00
backupdb.getByTypePaged(backups.BACKUP_TYPE_BOX, 1, 1000, function (error, result) {
2017-04-24 12:34:57 +02:00
expect(error).to.not.be.ok();
expect(result.length).to.equal(1);
expect(result[0].id).to.equal(BACKUP_1.id);
2020-06-14 15:09:04 -07:00
// check that app backups are also still there. backup_1 is still there
backupdb.get(BACKUP_1_APP_0.id, function (error, result) {
expect(error).to.not.be.ok();
expect(result.id).to.equal(BACKUP_1_APP_0.id);
done();
});
2017-04-24 12:34:57 +02:00
});
});
});
it('succeeds for app backups not referenced by a box backup', function (done) {
2020-06-14 15:09:04 -07:00
// add two dangling app backups not referenced by box backup. app1 is uninstalled. app0 is there
2019-04-13 17:14:04 -07:00
async.eachSeries([BACKUP_0_APP_0, BACKUP_0_APP_1], (b, done) => backupdb.add(b.id, b, done), function (error) {
expect(error).to.not.be.ok();
// wait for expiration
setTimeout(function () {
2021-07-12 23:35:30 -07:00
util.callbackify(cleanupBackups)(function (error) {
expect(error).to.not.be.ok();
2020-06-14 14:01:01 -07:00
backupdb.getByTypePaged(backups.BACKUP_TYPE_APP, 1, 1000, function (error, result) {
expect(error).to.not.be.ok();
2020-06-14 15:09:04 -07:00
expect(result.length).to.equal(3);
2021-06-03 12:20:44 -07:00
result = result.sort((r1, r2) => r1.id.localeCompare(r2.id));
2020-06-14 15:09:04 -07:00
expect(result[0].id).to.be(BACKUP_0_APP_0.id); // because app is installed, latest backup is preserved
expect(result[1].id).to.be(BACKUP_1_APP_0.id); // referenced by box
expect(result[2].id).to.be(BACKUP_1_APP_1.id); // referenced by box
done();
});
});
}, 2000);
});
});
2017-04-24 12:34:57 +02:00
});
2017-09-29 14:16:11 -07:00
2017-10-12 16:02:09 -07:00
describe('fs meta data', function () {
2021-06-03 12:20:44 -07:00
let tmpdir;
2017-09-29 14:16:11 -07:00
before(function () {
tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'backups-test'));
});
after(function () {
rimraf.sync(tmpdir);
});
2017-10-12 16:02:09 -07:00
it('saves special files', function (done) {
2017-09-29 14:16:11 -07:00
createTree(tmpdir, { 'data': { 'subdir': { 'emptydir': { } } }, 'dir2': { 'file': 'stuff' } });
2017-10-12 16:02:09 -07:00
fs.chmodSync(path.join(tmpdir, 'dir2/file'), parseInt('0755', 8));
2017-09-29 14:16:11 -07:00
2019-01-22 17:43:24 -08:00
let dataLayout = new DataLayout(tmpdir, []);
backups._saveFsMetadata(dataLayout, `${dataLayout.localRoot()}/fsmetadata.json`, function (error) {
2017-09-29 14:16:11 -07:00
expect(error).to.not.be.ok();
2017-10-12 16:02:09 -07:00
var emptyDirs = JSON.parse(fs.readFileSync(path.join(tmpdir, 'fsmetadata.json'), 'utf8')).emptyDirs;
2017-09-29 14:16:11 -07:00
expect(emptyDirs).to.eql(['./data/subdir/emptydir']);
2017-10-12 16:02:09 -07:00
var execFiles = JSON.parse(fs.readFileSync(path.join(tmpdir, 'fsmetadata.json'), 'utf8')).execFiles;
expect(execFiles).to.eql(['./dir2/file']);
2017-09-29 14:16:11 -07:00
done();
});
});
2017-10-12 16:02:09 -07:00
it('restores special files', function (done) {
2017-09-29 14:16:11 -07:00
rimraf.sync(path.join(tmpdir, 'data'));
expect(fs.existsSync(path.join(tmpdir, 'data/subdir/emptydir'))).to.be(false); // just make sure rimraf worked
2019-01-22 17:43:24 -08:00
let dataLayout = new DataLayout(tmpdir, []);
backups._restoreFsMetadata(dataLayout, `${dataLayout.localRoot()}/fsmetadata.json`, function (error) {
2017-09-29 14:16:11 -07:00
expect(error).to.not.be.ok();
expect(fs.existsSync(path.join(tmpdir, 'data/subdir/emptydir'))).to.be(true);
2017-10-12 16:02:09 -07:00
var mode = fs.statSync(path.join(tmpdir, 'dir2/file')).mode;
expect(mode & ~fs.constants.S_IFREG).to.be(parseInt('0755', 8));
2017-09-29 14:16:11 -07:00
done();
});
});
});
2017-10-02 20:08:00 -07:00
describe('filesystem', function () {
2021-06-03 12:20:44 -07:00
let backupInfo1;
2017-10-02 20:08:00 -07:00
2021-06-03 12:20:44 -07:00
const backupConfig = {
2017-10-02 20:08:00 -07:00
provider: 'filesystem',
backupFolder: path.join(os.tmpdir(), 'backups-test-filesystem'),
2020-05-14 20:05:27 -07:00
format: 'tgz',
2020-07-29 09:34:23 -07:00
retentionPolicy: { keepWithinSecs: 10000 },
schedulePattern: '00 00 23 * * *'
2017-10-02 20:08:00 -07:00
};
before(function (done) {
2020-05-14 20:05:27 -07:00
rimraf.sync(backupConfig.backupFolder);
2017-10-02 20:08:00 -07:00
done();
});
after(function (done) {
2020-05-14 20:05:27 -07:00
rimraf.sync(backupConfig.backupFolder);
2017-10-02 20:08:00 -07:00
done();
});
2021-06-03 12:20:44 -07:00
it('fails to set backup config for bad folder', function (done) {
const tmp = Object.assign({}, backupConfig, { backupFolder: '/root/oof' });
settings.setBackupConfig(tmp, function (error) {
2019-10-22 11:03:56 -07:00
expect(error).to.be.a(BoxError);
expect(error.reason).to.equal(BoxError.BAD_FIELD);
2017-10-02 20:08:00 -07:00
done();
});
});
it('succeeds to set backup config', function (done) {
2020-05-14 20:05:27 -07:00
settings.setBackupConfig(backupConfig, function (error) {
2017-10-02 20:08:00 -07:00
expect(error).to.be(null);
2021-06-03 12:20:44 -07:00
expect(fs.existsSync(path.join(backupConfig.backupFolder, 'snapshot'))).to.be(true); // auto-created
2017-10-02 20:08:00 -07:00
done();
});
});
it('can backup', function (done) {
// arch only has maria db which lacks some mysqldump options we need, this is only here to allow running the tests :-/
2019-05-07 15:31:32 +02:00
if (require('child_process').execSync('/usr/bin/mysqldump --version').toString().indexOf('MariaDB') !== -1) return done();
2017-10-02 20:08:00 -07:00
createBackup(function (error, result) {
expect(error).to.be(null);
2020-05-14 20:05:27 -07:00
expect(fs.statSync(path.join(backupConfig.backupFolder, 'snapshot/box.tar.gz')).nlink).to.be(2); // hard linked to a rotated backup
expect(fs.statSync(path.join(backupConfig.backupFolder, `${result.id}.tar.gz`)).nlink).to.be(2);
2017-10-02 20:08:00 -07:00
backupInfo1 = result;
done();
});
});
it('can take another backup', function (done) {
// arch only has maria db which lacks some mysqldump options we need, this is only here to allow running the tests :-/
2019-05-07 15:31:32 +02:00
if (require('child_process').execSync('/usr/bin/mysqldump --version').toString().indexOf('MariaDB') !== -1) return done();
2017-10-02 20:08:00 -07:00
createBackup(function (error, result) {
expect(error).to.be(null);
2020-05-14 20:05:27 -07:00
expect(fs.statSync(path.join(backupConfig.backupFolder, 'snapshot/box.tar.gz')).nlink).to.be(2); // hard linked to a rotated backup
expect(fs.statSync(path.join(backupConfig.backupFolder, `${result.id}.tar.gz`)).nlink).to.be(2); // hard linked to new backup
expect(fs.statSync(path.join(backupConfig.backupFolder, `${backupInfo1.id}.tar.gz`)).nlink).to.be(1); // not hard linked anymore
2017-10-02 20:08:00 -07:00
done();
});
});
});
2017-04-24 12:34:57 +02:00
});