cuttlefish
Version:
A simple lowlevel synchronizing library for Joyent Manta.
401 lines (353 loc) • 9.52 kB
JavaScript
var cuttlefish = require('../')
var manta = require('manta')
var test = require('tap').test
var client = require('./client.js')
// get all the details from the fixtures
var fs = require('fs')
var path = require('path')
var fix = path.resolve(__dirname, 'fixtures')
var names = 'abcdef'.split('')
var files = names.map(function(n) {
return [n, fs.statSync(fix + '/' + n)]
}).concat(names.map(function(n) {
return ['dir/' + n, fs.statSync(fix + '/dir/' + n)]
})).reduce(function(set, kv) {
set[kv[0]] = kv[1]
kv[1].headers = {
'access-control-allow-origin': '*'
}
kv[1].type = 'text/plain'
return set
}, {})
var mpath = '~~/stor/cuttlefish-testing'
var expectsum = {
a: 'YLcl8QychccNl4gN/oGRsw==',
b: 'O11cNxKVUEIhIxYXPM83vg==',
c: 'LNbuLHCwveU/vmysPIuLsQ==',
d: '4pMR9vG/GvkH+e+fRLgyiw==',
e: 'n/v0MSbjO+Us0r9+AdYn+Q==',
f: 'morZLFDK45qixWBP0KttjA==',
'dir/a': 'YLcl8QychccNl4gN/oGRsw==',
'dir/b': 'O11cNxKVUEIhIxYXPM83vg==',
'dir/c': 'LNbuLHCwveU/vmysPIuLsQ==',
'dir/d': '4pMR9vG/GvkH+e+fRLgyiw==',
'dir/e': 'n/v0MSbjO+Us0r9+AdYn+Q==',
'dir/f': 'morZLFDK45qixWBP0KttjA=='
}
var filesMd5 = Object.keys(files).reduce(function(set, f) {
var o = {}
for (var i in files[f]) o[i] = files[f][i]
o.md5 = expectsum[f]
set[f] = o
return set
}, {})
test('preclean', function(t) {
client.rmr(mpath, function(er) {
t.pass('cleaned')
t.end()
})
})
test('fs first sync, no checksums', function(t) {
var cf = cuttlefish({
files: files,
path: mpath,
client: client,
headers: {
'access-control-allow-methods': 'GET'
},
delete: true,
request: function(file, cb) {
var f = path.resolve(__dirname, 'fixtures', file.name)
cb(null, fs.createReadStream(f))
}
})
cf.on('file', function(file, status, data) {
t.equal(status, 'sent')
})
cf.on('complete', function(results) {
var res = Object.keys(results).sort().map(function(f) {
return [f, results[f]['computed-md5']]
}).reduce(function (set, kv) {
set[kv[0]] = kv[1]
return set
}, {})
t.same(res, expectsum)
t.end()
})
})
test('fs second sync, no checksums', function(t) {
var expectStatus = Object.keys(expectsum).reduce(function(set, k) {
set[k] = 'match'
return set
}, {})
var cf = cuttlefish({
files: files,
path: mpath,
client: client,
headers: {
'access-control-allow-methods': 'GET'
},
request: function(file, cb) {
var f = path.resolve(__dirname, 'fixtures', file.name)
cb(null, fs.createReadStream(f))
}
})
cf.on('file', function(file, status, data) {
t.equal(status, 'match')
})
cf.on('complete', function(results) {
var res = Object.keys(results).sort().reduce(function(set, f) {
set[f] = results[f].status
return set
}, {})
t.same(res, expectStatus)
t.end()
})
})
test('fs second sync, with checksums', function(t) {
var cf = cuttlefish({
files: filesMd5,
path: mpath,
client: client,
headers: {
'access-control-allow-methods': 'GET'
},
request: function(file, cb) {
var f = path.resolve(__dirname, 'fixtures', file.name)
cb(null, fs.createReadStream(f))
}
})
cf.on('file', function(file, status, data) {
t.equal(status, 'match')
})
cf.on('complete', function(results) {
var res = Object.keys(results).sort().reduce(function(set, f) {
set[f] = results[f].md5 || results[f]['computed-md5']
return set
}, {})
t.same(res, expectsum)
t.end()
})
})
test('fs partial sync', function(t) {
client.rmr(mpath + '/dir', function(er) {
if (er)
throw er
var cf = cuttlefish({
files: files,
path: mpath,
client: client,
headers: {
'access-control-allow-methods': 'GET'
},
request: function(file, cb) {
var f = path.resolve(__dirname, 'fixtures', file.name)
cb(null, fs.createReadStream(f))
}
})
cf.on('file', function(file, status, data) {
if (file.name.match(/^dir/))
t.equal(status, 'sent')
else
t.equal(status, 'match')
})
cf.on('complete', function(results) {
var expect = Object.keys(expectsum).sort().reduce(function(set, f) {
if (f.match(/^dir/))
set[f] = expectsum[f]
else
set[f] = 'match'
return set
}, {})
var res = Object.keys(results).sort().map(function(f) {
return [f, results[f].md5 || results[f].status]
}).reduce(function (set, kv) {
set[kv[0]] = kv[1]
return set
}, {})
t.same(res, expect)
t.end()
})
})
})
test('fs delete extra', function(t) {
delete files['dir/a']
delete files.a
delete filesMd5['dir/a']
delete filesMd5.a
delete expectsum['dir/a']
delete expectsum.a
var deletedExpect = [ 'a', 'dir/a' ]
var expect = Object.keys(expectsum).map(function(f) {
return [f, expectsum[f]]
}).concat(deletedExpect.map(function(f) {
return [f, 'delete']
})).reduce(function(set, kv) {
set[kv[0]] = kv[1]
return set
}, {})
var cf = cuttlefish({
files: filesMd5,
path: mpath,
client: client,
headers: {
'access-control-allow-methods': 'GET'
},
request: function(file, cb) {
var f = path.resolve(__dirname, 'fixtures', file.name)
cb(null, fs.createReadStream(f))
},
delete: true
})
cf.on('file', function(file, status, data) {
t.equal(status, 'match')
})
var deleted = []
cf.on('delete', function(f) {
deleted.push(f)
})
cf.on('complete', function(results) {
t.same(deleted.sort(), deletedExpect, 'deleted the right files')
var res = Object.keys(results).sort().reduce(function(set, f) {
set[f] = results[f].md5 || results[f].status
return set
}, {})
t.same(res, expect)
t.end()
})
})
test('dry-run test', function(t) {
delete files['dir/b']
delete files.b
delete filesMd5['dir/b']
delete filesMd5.b
delete expectsum['dir/b']
delete expectsum.b
// delete a remote one so that we have a dry-run create as well.
client.unlink(mpath + '/c', function(er, res) {
if (er)
throw er
var cf = cuttlefish({
delete: true,
dryRun: true,
files: files,
client: client,
request: function(file, cb) {
throw new Error('Should not try to load any files: ' + file)
},
path: mpath
})
var expectMatch = [ 'd', 'e', 'f', 'dir/c', 'dir/d', 'dir/e', 'dir/f' ]
var expectSent = [ 'c' ]
var expectDeleted = [ 'b', 'dir/b' ]
var deleted = []
cf.on('delete', function(f) {
deleted.push(f)
})
var sent = []
cf.on('send', function(f) {
sent.push(f.name)
})
var match = []
cf.on('match', function(f) {
match.push(f.name)
})
cf.on('complete', function(results) {
t.same(deleted, expectDeleted)
t.same(sent, expectSent)
t.same(match, expectMatch)
// make sure the deleted file is still there.
client.info(mpath + '/' + deleted[0], function(er, info) {
if (er)
throw er
t.ok(info)
// make sure the sent file is still not there
client.info(mpath + '/' + sent[0], function(er, info) {
t.ok(er)
t.equal(er.statusCode, 404)
t.end()
})
})
})
})
})
test('skip test', function(t) {
// here, we don't provide length/md5 about one of the files,
// but we just accept whatever the remote has for it.
// also delete dir/c, since we removed that in the last test.
delete files.c
files.b = { skip: true }
files['dir/b'] = { skip: true }
// don't send a skip:true file, even if it is not on the remote
files.doNotSend = { skip: true }
var cf = cuttlefish({
delete: true,
files: files,
client: client,
request: function(file, cb) {
throw new Error('Should not try to load any files: ' + file)
},
path: mpath
})
var expectMatch = [
'b', 'd', 'e', 'f',
'dir/b', 'dir/c', 'dir/d', 'dir/e', 'dir/f'
]
cf.on('delete', function(f) {
t.fail('should not delete anything: ' + f)
})
cf.on('send', function(f) {
t.fail('should not send anything: ' + f.name)
})
var match = []
cf.on('match', function(f) {
match.push(f.name)
})
cf.on('complete', function(results) {
t.same(match, expectMatch)
t.end()
})
})
test('fs only delete extra', function(t) {
delete files['dir/b']
delete files.b
var deletedExpect = [ 'b', 'dir/b' ]
var cf = cuttlefish({
onlyDelete: true,
files: files,
path: mpath,
client: client,
headers: {
'access-control-allow-methods': 'GET'
},
request: function(file, cb) {
var f = path.resolve(__dirname, 'fixtures', file.name)
cb(null, fs.createReadStream(f))
}
})
var deleted = []
cf.on('delete', function(f) {
deleted.push(f)
})
cf.on('complete', function(results) {
t.same(deleted.sort(), deletedExpect, 'deleted the right files')
t.end()
})
})
// make sure that we got the right headers
test('custom headers', function(t) {
client.info(mpath + '/f', function(er, res) {
if (er)
throw er
t.equal(res.headers['access-control-allow-origin'], '*')
t.equal(res.headers['access-control-allow-methods'], 'GET')
t.end()
})
})
test('postclean', function(t) {
client.rmr(mpath, function(er) {
client.close()
t.pass('cleaned')
t.end()
})
})