diff --git a/src/progress.js b/src/progress.js index e4fe95bff..203268192 100644 --- a/src/progress.js +++ b/src/progress.js @@ -12,6 +12,7 @@ exports = module.exports = { }; var assert = require('assert'), + config = require('./config.js'), debug = require('debug')('box:progress'); // if progress.update or progress.backup are object, they will contain 'percent' and 'message' properties @@ -41,6 +42,8 @@ function setDetail(tag, detail) { assert.strictEqual(typeof tag, 'string'); assert.strictEqual(typeof detail, 'string'); + if (config.TEST && !progress[tag]) progress[tag] = { }; + progress[tag].detail = detail; } @@ -55,4 +58,3 @@ function clear(tag) { function getAll() { return progress; } - diff --git a/src/storage/filesystem.js b/src/storage/filesystem.js index d46095474..ac3580fc0 100644 --- a/src/storage/filesystem.js +++ b/src/storage/filesystem.js @@ -86,7 +86,7 @@ function downloadDir(apiConfig, backupFilePath, destDir, callback) { shell.exec('downloadDir', '/bin/cp', [ '-r', backupFilePath + '/.', destDir ], { }, function (error) { if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error.message)); - callback(); + callback(null); }); } @@ -105,7 +105,7 @@ function copy(apiConfig, oldFilePath, newFilePath, callback) { shell.exec('copy', '/bin/cp', [ '-al', oldFilePath, newFilePath ], { }, function (error) { if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error.message)); - callback(); + callback(null); }); }); } @@ -124,7 +124,7 @@ function remove(apiConfig, filename, callback) { if (!safe.fs.rmdirSync(filename)) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, safe.error.message)); } - callback(); + callback(null); } function removeDir(apiConfig, pathPrefix, callback) { @@ -135,7 +135,7 @@ function removeDir(apiConfig, pathPrefix, callback) { shell.exec('removeDir', '/bin/rm', [ '-rf', pathPrefix ], { }, function (error) { if (error) return callback(new BackupsError(BackupsError.EXTERNAL_ERROR, error.message)); - callback(); + callback(null); }); } diff --git a/src/storage/noop.js b/src/storage/noop.js index 8ea244a0e..a67923e7f 100644 --- a/src/storage/noop.js +++ b/src/storage/noop.js @@ -25,7 +25,7 @@ function upload(apiConfig, backupFilePath, sourceStream, callback) { debug('upload: %s', backupFilePath); - callback(); + callback(null); } function download(apiConfig, backupFilePath, callback) { @@ -46,7 +46,7 @@ function downloadDir(apiConfig, backupFilePath, destDir, callback) { debug('downloadDir: %s -> %s', backupFilePath, destDir); - callback(); + callback(new Error('Cannot download from noop backend')); } function copy(apiConfig, oldFilePath, newFilePath, callback) { @@ -57,7 +57,7 @@ function copy(apiConfig, oldFilePath, newFilePath, callback) { debug('copy: %s -> %s', oldFilePath, newFilePath); - callback(); + callback(null); } function remove(apiConfig, filename, callback) { @@ -67,7 +67,7 @@ function remove(apiConfig, filename, callback) { debug('remove: %s', filename); - callback(); + callback(null); } function removeDir(apiConfig, pathPrefix, callback) { @@ -77,14 +77,14 @@ function removeDir(apiConfig, pathPrefix, callback) { debug('removeDir: %s', pathPrefix); - callback(); + callback(null); } function testConfig(apiConfig, callback) { assert.strictEqual(typeof apiConfig, 'object'); assert.strictEqual(typeof callback, 'function'); - callback(); + callback(null); } function backupDone(apiConfig, backupId, appBackupIds, callback) { @@ -93,5 +93,5 @@ function backupDone(apiConfig, backupId, appBackupIds, callback) { assert(Array.isArray(appBackupIds)); assert.strictEqual(typeof callback, 'function'); - callback(); + callback(null); } diff --git a/src/storage/s3.js b/src/storage/s3.js index fe2a6b687..d898a08c4 100644 --- a/src/storage/s3.js +++ b/src/storage/s3.js @@ -189,7 +189,7 @@ function listDir(apiConfig, backupFilePath, options, iteratorCallback, callback) }); }); }, function (error) { - if (error.message === 'Done') return callback(); + if (error.message === 'Done') return callback(null); callback(error); }); diff --git a/src/test/backups-test.js b/src/test/backups-test.js index 086e099e6..ee9a53994 100644 --- a/src/test/backups-test.js +++ b/src/test/backups-test.js @@ -15,9 +15,80 @@ var async = require('async'), expect = require('expect.js'), fs = require('fs'), os = require('os'), + mkdirp = require('mkdirp'), + readdirp = require('readdirp'), path = require('path'), + progress = require('../progress.js'), rimraf = require('rimraf'), - settings = require('../settings.js'); + settings = require('../settings.js'), + SettingsError = require('../settings.js').SettingsError; + +function compareDirectories(one, two, callback) { + readdirp({ root: one }, function (error, treeOne) { + if (error) return callback(error); + + readdirp({ root: two }, function (error, treeTwo) { + if (error) return callback(error); + + var mismatch = []; + + function compareDirs(a, b) { + a.forEach(function (tmpA) { + var found = b.find(function (tmpB) { + return tmpA.path === tmpB.path; + }); + + if (!found) mismatch.push(tmpA); + }); + } + + function compareFiles(a, b) { + a.forEach(function (tmpA) { + var found = b.find(function (tmpB) { + // TODO check file or symbolic link + return tmpA.path === tmpB.path && tmpA.mode === tmpB.mode; + }); + + if (!found) mismatch.push(tmpA); + }); + } + + compareDirs(treeOne.directories, treeTwo.directories); + compareDirs(treeTwo.directories, treeOne.directories); + compareFiles(treeOne.files, treeTwo.files); + compareFiles(treeTwo.files, treeOne.files); + + if (mismatch.length) { + console.error('Files not found in both: %j', mismatch); + return callback(new Error('file mismatch')); + } + + callback(null); + }); + }); +} + +function createBackup(callback) { + backups.backup({ username: 'test' }, function (error) { // this call does not wait for the backup! + if (error) return callback(error); + + function waitForBackup() { + var p = progress.getAll(); + if (p.backup.percent !== 100) return setTimeout(waitForBackup, 1000); + + if (p.backup.message) return callback(new Error('backup failed:' + p.backup.message)); + + backups.getByStatePaged(backupdb.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')); + + callback(null, result[0]); + }); + } + + setTimeout(waitForBackup, 1000); + }); +} describe('backups', function () { before(function (done) { @@ -209,4 +280,72 @@ describe('backups', function () { }); }); }); + + describe('filesystem', function () { + var backupInfo1; + + var gBackupConfig = { + provider: 'filesystem', + backupFolder: path.join(os.tmpdir(), 'backups-test-filesystem'), + format: 'tgz' + }; + + before(function (done) { + rimraf.sync(gBackupConfig.backupFolder); + + done(); + }); + + after(function (done) { + rimraf.sync(gBackupConfig.backupFolder); + progress.clear(progress.BACKUP); + done(); + }); + + it('fails to set backup config for non-existing folder', function (done) { + settings.setBackupConfig(gBackupConfig, function (error) { + expect(error).to.be.a(SettingsError); + expect(error.reason).to.equal(SettingsError.BAD_FIELD); + + done(); + }); + }); + + it('succeeds to set backup config', function (done) { + mkdirp.sync(gBackupConfig.backupFolder); + + settings.setBackupConfig(gBackupConfig, function (error) { + expect(error).to.be(null); + + done(); + }); + }); + + it('can backup', function (done) { + this.timeout(6000); + + createBackup(function (error, result) { + expect(error).to.be(null); + expect(fs.statSync(path.join(gBackupConfig.backupFolder, 'snapshot/box.tar.gz')).nlink).to.be(2); // hard linked to a rotated backup + expect(fs.statSync(path.join(gBackupConfig.backupFolder, `${result.id}.tar.gz`)).nlink).to.be(2); + + backupInfo1 = result; + + done(); + }); + }); + + it('can take another backup', function (done) { + this.timeout(6000); + + createBackup(function (error, result) { + expect(error).to.be(null); + expect(fs.statSync(path.join(gBackupConfig.backupFolder, 'snapshot/box.tar.gz')).nlink).to.be(2); // hard linked to a rotated backup + expect(fs.statSync(path.join(gBackupConfig.backupFolder, `${result.id}.tar.gz`)).nlink).to.be(2); // hard linked to new backup + expect(fs.statSync(path.join(gBackupConfig.backupFolder, `${backupInfo1.id}.tar.gz`)).nlink).to.be(1); // not hard linked anymore + + done(); + }); + }); + }); }); diff --git a/src/test/server-test.js b/src/test/server-test.js index 67f45ae59..922e2b3b2 100644 --- a/src/test/server-test.js +++ b/src/test/server-test.js @@ -6,12 +6,11 @@ 'use strict'; -var async = require('async'), - progress = require('../progress.js'), - config = require('../config.js'), +var config = require('../config.js'), database = require('../database.js'), expect = require('expect.js'), nock = require('nock'), + progress = require('../progress.js'), superagent = require('superagent'), server = require('../server.js'); diff --git a/src/test/storage-test.js b/src/test/storage-test.js index beda5eb27..08d595d9d 100644 --- a/src/test/storage-test.js +++ b/src/test/storage-test.js @@ -5,100 +5,22 @@ 'use strict'; -var async = require('async'), - backups = require('../backups.js'), - BackupsError = require('../backups.js').BackupsError, - config = require('../config.js'), - database = require('../database.js'), +var BackupsError = require('../backups.js').BackupsError, expect = require('expect.js'), filesystem = require('../storage/filesystem.js'), fs = require('fs'), - mkdirp = require('mkdirp'), MockS3 = require('mock-aws-s3'), + noop = require('../storage/noop.js'), os = require('os'), path = require('path'), - progress = require('../progress.js'), - readdirp = require('readdirp'), rimraf = require('rimraf'), - s3 = require('../storage/s3.js'), - settings = require('../settings.js'), - SettingsError = settings.SettingsError; - -function setup(done) { - config.set('provider', 'caas'); - - async.series([ - database.initialize, - settings.initialize, - function (callback) { - // a cloudron must have a backup config to startup - settings.setBackupConfig({ provider: 'filesystem', format: 'tgz', backupFolder: '/tmp'}, function (error) { - expect(error).to.be(null); - callback(); - }); - } - ], done); -} - -function cleanup(done) { - async.series([ - settings.uninitialize, - database._clear - ], done); -} - -function compareDirectories(one, two, callback) { - readdirp({ root: one }, function (error, treeOne) { - if (error) return callback(error); - - readdirp({ root: two }, function (error, treeTwo) { - if (error) return callback(error); - - var mismatch = []; - - function compareDirs(a, b) { - a.forEach(function (tmpA) { - var found = b.find(function (tmpB) { - return tmpA.path === tmpB.path; - }); - - if (!found) mismatch.push(tmpA); - }); - } - - function compareFiles(a, b) { - a.forEach(function (tmpA) { - var found = b.find(function (tmpB) { - // TODO check file or symbolic link - return tmpA.path === tmpB.path && tmpA.mode === tmpB.mode; - }); - - if (!found) mismatch.push(tmpA); - }); - } - - compareDirs(treeOne.directories, treeTwo.directories); - compareDirs(treeTwo.directories, treeOne.directories); - compareFiles(treeOne.files, treeTwo.files); - compareFiles(treeTwo.files, treeOne.files); - - if (mismatch.length) { - console.error('Files not found in both: %j', mismatch); - return callback(new Error('file mismatch')); - } - - callback(null); - }); - }); -} + s3 = require('../storage/s3.js'); describe('Storage', function () { describe('filesystem', function () { - var gBackupId_1; - var gBackupId_2; + var gTmpFolder; - var gSourceFolder; - var gDestinationFolder; + var gBackupConfig = { provider: 'filesystem', key: 'key', @@ -107,130 +29,171 @@ describe('Storage', function () { }; before(function (done) { - setup(function (error) { - expect(error).to.be(null); + gTmpFolder = fs.mkdtempSync(path.join(os.tmpdir(), 'filesystem-storage-test_')); - gTmpFolder = fs.mkdtempSync(path.join(os.tmpdir(), 'filesystem-backup-test_')); + gBackupConfig.backupFolder = path.join(gTmpFolder, 'backups/'); - gBackupConfig.backupFolder = path.join(gTmpFolder, 'backups/'); - gSourceFolder = path.join(__dirname, 'storage'); - gDestinationFolder = path.join(gTmpFolder, 'destination/'); - - gBackupId_1 = backups._getBackupFilePath(gBackupConfig, 'someprefix/one', gBackupConfig.format); - gBackupId_2 = backups._getBackupFilePath(gBackupConfig, 'someprefix/two', gBackupConfig.format); - - done(); - }); + done(); }); after(function (done) { - cleanup(function (error) { + rimraf.sync(gTmpFolder); + done(); + }); + + it('can upload', function (done) { + var sourceFile = path.join(__dirname, 'storage/data/test.txt'); + var sourceStream = fs.createReadStream(sourceFile); + var destFile = gTmpFolder + '/uploadtest/test.txt'; + filesystem.upload(gBackupConfig, destFile, sourceStream, function (error) { expect(error).to.be(null); - rimraf.sync(gTmpFolder); + expect(fs.existsSync(destFile)); + expect(fs.statSync(sourceFile).size).to.be(fs.statSync(destFile).size); done(); }); }); - it('fails to set backup config for non-existing folder', function (done) { - settings.setBackupConfig(gBackupConfig, function (error) { - expect(error).to.be.a(SettingsError); - expect(error.reason).to.equal(SettingsError.BAD_FIELD); - + it('upload waits for empty file to be created', function (done) { + var sourceFile = path.join(__dirname, 'storage/data/empty'); + var sourceStream = fs.createReadStream(sourceFile); + var destFile = gTmpFolder + '/uploadtest/empty'; + filesystem.upload(gBackupConfig, destFile, sourceStream, function (error) { + expect(error).to.be(null); + expect(fs.existsSync(destFile)); + expect(fs.statSync(sourceFile).size).to.be(fs.statSync(destFile).size); done(); }); }); - it('succeeds to set backup config', function (done) { - mkdirp.sync(gBackupConfig.backupFolder); - - settings.setBackupConfig(gBackupConfig, function (error) { + it('upload unlinks old file', function (done) { + var sourceFile = path.join(__dirname, 'storage/data/test.txt'); + var sourceStream = fs.createReadStream(sourceFile); + var destFile = gTmpFolder + '/uploadtest/test.txt'; + var oldStat = fs.statSync(destFile); + filesystem.upload(gBackupConfig, destFile, sourceStream, function (error) { expect(error).to.be(null); - + expect(fs.existsSync(destFile)).to.be(true); + expect(fs.statSync(sourceFile).size).to.be(fs.statSync(destFile).size); + expect(oldStat.inode).to.not.be(fs.statSync(destFile).size); done(); }); }); - it('can backup', function (done) { - var tarStream = backups._createTarPackStream(gSourceFolder, gBackupConfig.key); - filesystem.upload(gBackupConfig, gBackupId_1, tarStream, function (error) { - expect(error).to.be(null); + it('can download file', function (done) { + var sourceFile = gTmpFolder + '/uploadtest/test.txt'; + filesystem.download(gBackupConfig, sourceFile, function (error, stream) { + expect(error).to.be(null); + expect(stream).to.be.an('object'); done(); }); }); - it('can download', function (done) { - filesystem.download(gBackupConfig, gBackupId_1, function (error, stream) { - expect(error).to.be(null); - - backups._tarExtract(stream, gDestinationFolder, gBackupConfig.key || null, function (error) { - expect(error).to.be(null); - - compareDirectories(path.join(gSourceFolder, 'data'), path.join(gDestinationFolder, 'data'), function (error) { - expect(error).to.equal(null); - - compareDirectories(path.join(gSourceFolder, 'addon'), path.join(gDestinationFolder, 'addon'), function (error) { - expect(error).to.equal(null); - - rimraf(gDestinationFolder, done); - }); - }); - }); - }); - }); - - it('can copy backup', function (done) { - // will be verified after removing the first and restoring from the copy - filesystem.copy(gBackupConfig, gBackupId_1, gBackupId_2, done); - }); - - it('can remove backup', function (done) { - // will be verified with next test trying to download the removed one - filesystem.remove(gBackupConfig, gBackupId_1, done); - }); - - it('cannot download deleted backup', function (done) { - filesystem.download(gBackupConfig, gBackupId_1, function (error, stream) { - expect(error).to.be.an('object'); - expect(error.reason).to.equal(BackupsError.NOT_FOUND); + it('download errors for missing file', function (done) { + var sourceFile = gTmpFolder + '/uploadtest/missing'; + filesystem.download(gBackupConfig, sourceFile, function (error) { + expect(error.reason).to.be(BackupsError.NOT_FOUND); done(); }); }); - it('can download backup copy', function (done) { - filesystem.download(gBackupConfig, gBackupId_2, function (error, stream) { + it('download dir copies contents of source dir', function (done) { + var sourceDir = path.join(__dirname, 'storage'); + + filesystem.downloadDir(gBackupConfig, sourceDir, gTmpFolder, function (error) { expect(error).to.be(null); - - backups._tarExtract(stream, gDestinationFolder, gBackupConfig.key || null, function (error) { - expect(error).to.be(null); - - compareDirectories(path.join(gSourceFolder, 'data'), path.join(gDestinationFolder, 'data'), function (error) { - expect(error).to.equal(null); - - compareDirectories(path.join(gSourceFolder, 'addon'), path.join(gDestinationFolder, 'addon'), function (error) { - expect(error).to.equal(null); - - rimraf(gDestinationFolder, done); - }); - }); - }); + expect(fs.statSync(path.join(gTmpFolder, 'data/empty')).size).to.be(0); + done(); }); }); - it('can remove backup copy', function (done) { - filesystem.remove(gBackupConfig, gBackupId_2, done); + it('can copy', function (done) { + var sourceFile = gTmpFolder + '/uploadtest/test.txt'; // keep the test within save device + var destFile = gTmpFolder + '/uploadtest/test-hardlink.txt'; + + filesystem.copy(gBackupConfig, sourceFile, destFile, function (error) { + expect(error).to.be(null); + expect(fs.statSync(destFile).nlink).to.be(2); // created a hardlink + done(); + }); + }); + + it('can remove file', function (done) { + var sourceFile = gTmpFolder + '/uploadtest/test-hardlink.txt'; + + filesystem.remove(gBackupConfig, sourceFile, function (error) { + expect(error).to.be(null); + expect(fs.existsSync(sourceFile)).to.be(false); + done(); + }); + }); + + it('can remove empty dir', function (done) { + var sourceDir = gTmpFolder + '/emptydir'; + fs.mkdirSync(sourceDir); + + filesystem.remove(gBackupConfig, sourceDir, function (error) { + expect(error).to.be(null); + expect(fs.existsSync(sourceDir)).to.be(false); + done(); + }); + }); + }); + + describe('noop', function () { + var gBackupConfig = { + provider: 'noop', + format: 'tgz' + }; + + it('upload works', function (done) { + noop.upload(gBackupConfig, 'file', { }, function (error) { + expect(error).to.be(null); + done(); + }); + }); + + it('can download file', function (done) { + noop.download(gBackupConfig, 'file', function (error) { + expect(error).to.be.an(Error); + done(); + }); + }); + + it('download dir copies contents of source dir', function (done) { + noop.downloadDir(gBackupConfig, 'sourceDir', 'destDir', function (error) { + expect(error).to.be.an(Error); + done(); + }); + }); + + it('can copy', function (done) { + noop.copy(gBackupConfig, 'sourceFile', 'destFile', function (error) { + expect(error).to.be(null); + done(); + }); + }); + + it('can remove file', function (done) { + noop.remove(gBackupConfig, 'sourceFile', function (error) { + expect(error).to.be(null); + done(); + }); + }); + + it('can remove empty dir', function (done) { + noop.remove(gBackupConfig, 'sourceDir', function (error) { + expect(error).to.be(null); + done(); + }); }); }); describe('s3', function () { this.timeout(10000); - var gBackupId_1 = 'someprefix/one'; - var gBackupId_2 = 'someprefix/two'; - var gTmpFolder; - var gSourceFolder; - var gDestinationFolder; + var gS3Folder; var gBackupConfig = { provider: 's3', key: 'key', @@ -242,113 +205,76 @@ describe('Storage', function () { format: 'tgz' }; - before(function (done) { + before(function () { MockS3.config.basePath = path.join(os.tmpdir(), 's3-backup-test-buckets/'); + rimraf.sync(MockS3.config.basePath); + gS3Folder = path.join(MockS3.config.basePath, gBackupConfig.bucket); s3._mockInject(MockS3); - - setup(function (error) { - expect(error).to.be(null); - - gTmpFolder = fs.mkdtempSync(path.join(os.tmpdir(), 's3-backup-test_')); - gSourceFolder = path.join(__dirname, 'storage'); - gDestinationFolder = path.join(gTmpFolder, 'destination/'); - - settings.setBackupConfig(gBackupConfig, function (error) { - expect(error).to.be(null); - - done(); - }); - }); }); - after(function (done) { + after(function () { s3._mockRestore(); rimraf.sync(MockS3.config.basePath); + }); - cleanup(function (error) { + it('can upload', function (done) { + var sourceFile = path.join(__dirname, 'storage/data/test.txt'); + var sourceStream = fs.createReadStream(sourceFile); + var destKey = 'uploadtest/test.txt'; + s3.upload(gBackupConfig, destKey, sourceStream, function (error) { expect(error).to.be(null); - rimraf.sync(gTmpFolder); + expect(fs.existsSync(path.join(gS3Folder, destKey))).to.be(true); + expect(fs.statSync(path.join(gS3Folder, destKey)).size).to.be(fs.statSync(sourceFile).size); done(); }); }); - it('can backup', function (done) { - var tarStream = backups._createTarPackStream(gSourceFolder, gBackupConfig.key); - s3.upload(gBackupConfig, gBackupId_1, tarStream, function (error) { + it('can download file', function (done) { + var sourceKey = 'uploadtest/test.txt'; + s3.download(gBackupConfig, sourceKey, function (error, stream) { expect(error).to.be(null); - + expect(stream).to.be.an('object'); done(); }); }); - it('can download', function (done) { - s3.download(gBackupConfig, gBackupId_1, function (error, stream) { + it('download dir copies contents of source dir', function (done) { + var sourceFile = path.join(__dirname, 'storage/data/test.txt'); + var sourceKey = ''; + var destDir = path.join(os.tmpdir(), 's3-destdir'); + + s3.downloadDir(gBackupConfig, sourceKey, destDir, function (error) { expect(error).to.be(null); - - backups._tarExtract(stream, gDestinationFolder, gBackupConfig.key || null, function (error) { - expect(error).to.be(null); - - compareDirectories(path.join(gSourceFolder, 'data'), path.join(gDestinationFolder, 'data'), function (error) { - expect(error).to.equal(null); - - compareDirectories(path.join(gSourceFolder, 'addon'), path.join(gDestinationFolder, 'addon'), function (error) { - expect(error).to.equal(null); - - rimraf(gDestinationFolder, done); - }); - }); - }); + expect(fs.statSync(path.join(destDir, 'uploadtest/test.txt')).size).to.be(fs.statSync(sourceFile).size); + done(); }); }); - it('can copy backup', function (done) { - // will be verified after removing the first and restoring from the copy - progress.set(progress.BACKUP, 10, 'Testing'); + it('can copy', function (done) { + var sourceKey = 'uploadtest'; - s3.copy(gBackupConfig, gBackupId_1, gBackupId_2, done); - }); - - it('can remove backup', function (done) { - // will be verified with next test trying to download the removed one - s3.remove(gBackupConfig, gBackupId_1, done); - }); - - it('cannot download deleted backup', function (done) { - s3.download(gBackupConfig, gBackupId_1, function (error, stream) { + s3.copy(gBackupConfig, sourceKey, 'uploadtest-copy', function (error) { + var sourceFile = path.join(__dirname, 'storage/data/test.txt'); expect(error).to.be(null); - - stream.on('error', function (error) { - expect(error).to.be.an('object'); - expect(error.reason).to.equal(BackupsError.NOT_FOUND); - - done(); - }); + expect(fs.statSync(path.join(gS3Folder, 'uploadtest-copy/test.txt')).size).to.be(fs.statSync(sourceFile).size); + done(); }); }); - it('can download backup copy', function (done) { - s3.download(gBackupConfig, gBackupId_2, function (error, stream) { + it('can remove file', function (done) { + s3.remove(gBackupConfig, 'uploadtest-copy/test.txt', function (error) { expect(error).to.be(null); - - backups._tarExtract(stream, gDestinationFolder, gBackupConfig.key || null, function (error) { - expect(error).to.be(null); - - compareDirectories(path.join(gSourceFolder, 'data'), path.join(gDestinationFolder, 'data'), function (error) { - expect(error).to.equal(null); - - compareDirectories(path.join(gSourceFolder, 'addon'), path.join(gDestinationFolder, 'addon'), function (error) { - expect(error).to.equal(null); - - rimraf(gDestinationFolder, done); - }); - }); - }); + expect(fs.existsSync(path.join(gS3Folder, 'uploadtest-copy/test.txt'))).to.be(false); + done(); }); }); - it('can remove backup copy', function (done) { - s3.remove(gBackupConfig, gBackupId_2, done); + it('can remove non-existent dir', function (done) { + noop.remove(gBackupConfig, 'blah', function (error) { + expect(error).to.be(null); + done(); + }); }); }); }); diff --git a/src/test/storage/data/empty b/src/test/storage/data/empty new file mode 100644 index 000000000..e69de29bb