makedrive
Version:
Webmaker Filesystem
592 lines (469 loc) • 19.2 kB
JavaScript
var expect = require('chai').expect;
var util = require('../../lib/util.js');
var MakeDrive = require('../../../client/src');
var Filer = require('../../../lib/filer.js');
var SyncMessage = require('../../../lib/syncmessage');
var WebSocketServer = require('ws').Server;
var rsync = require("../../../lib/rsync");
var rsyncOptions = require('../../../lib/constants').rsyncDefaults;
describe('MakeDrive Client API', function(){
describe('Core API', function() {
var provider;
beforeEach(function(done) {
util.ready(function() {
provider = new Filer.FileSystem.providers.Memory(util.username());
done();
});
});
afterEach(function() {
provider = null;
});
it('should have expected methods and properites', function() {
// Bits copied from Filer
expect(MakeDrive.Buffer).to.be.a.function;
expect(MakeDrive.Path).to.exist;
expect(MakeDrive.Path.normalize).to.be.a.function;
expect(MakeDrive.Errors).to.exist;
// MakeDrive.fs()
expect(MakeDrive.fs).to.be.a.function;
var fs = MakeDrive.fs({memory: true, manual: true});
var fs2 = MakeDrive.fs({memory: true, manual: true});
expect(fs).to.equal(fs2);
// MakeDrive.fs().sync property
expect(fs.sync).to.exist;
expect(fs.sync.on).to.be.a.function;
expect(fs.sync.off).to.be.a.function;
expect(fs.sync.connect).to.be.a.function;
expect(fs.sync.disconnect).to.be.a.function;
expect(fs.sync.sync).to.be.a.function;
// Sync States
expect(fs.sync.SYNC_DISCONNECTED).to.equal("SYNC DISCONNECTED");
expect(fs.sync.SYNC_CONNECTING).to.equal("SYNC CONNECTING");
expect(fs.sync.SYNC_CONNECTED).to.equal("SYNC CONNECTED");
expect(fs.sync.SYNC_SYNCING).to.equal("SYNC SYNCING");
expect(fs.sync.SYNC_ERROR).to.equal("SYNC ERROR");
expect(fs.sync.state).to.equal(fs.sync.SYNC_DISCONNECTED);
});
it('should allow passing options to Filer from MakeDrive.fs(options)', function(done) {
var fs = MakeDrive.fs({
forceCreate: true,
provider: provider,
flags: ['FORMAT', 'NOATIME', 'NOCTIME', 'NOMTIME'],
});
// Since we set custom flags to disable time stamps,
// any fs access should leave the times unchanged.
fs.stat('/', function(err, stats) {
if(err) throw err;
// Remember the original mtime on the root dir
var rootMTIME = stats.mtime;
// Write a file within /, which will update the root dir
fs.writeFile('/file', 'data', function(err) {
if(err) throw err;
// Make sure the mtime on / is the same as before
fs.stat('/', function(err, stats) {
if(err) throw err;
expect(stats.mtime).to.equal(rootMTIME);
done();
});
});
});
});
/**
* This test goes through the complete process of syncing with the server.
* It starts by connecting, then writes a file and tries to sync. The
* various sync events are observed, then it disconnects, and finally
* checks that the file was uploaded.
*/
it('should go through proper steps with connect(), request(), disconnect()', function(done) {
util.authenticatedConnection(function( err, result ) {
expect(err).not.to.exist;
var token = result.token;
var layout = {'/file': 'data'};
var fs = MakeDrive.fs({provider: provider, manual: true, forceCreate: true, autoReconnect: false});
var sync = fs.sync;
var everSeenSyncing = false;
var everSeenCompleted = false;
var everSeenError = false;
sync.once('connected', function onConnected() {
expect(sync.state).to.equal(sync.SYNC_CONNECTED);
// Write a file and try to sync
util.createFilesystemLayout(fs, layout, function(err) {
expect(err).not.to.exist;
sync.request();
});
});
sync.once('syncing', function onUpstreamSyncing() {
everSeenSyncing = sync.state;
});
sync.once('completed', function onUpstreamCompleted() {
everSeenCompleted = sync.state;
// Confirm file was really uploaded and remote fs matches what we expect
util.ensureRemoteFilesystem(layout, result.jar, function() {
sync.disconnect();
});
});
sync.on('error', function onError(err) {
// Remember any errors we see--should be none
everSeenError = err;
});
sync.once('disconnected', function onDisconnected() {
expect(everSeenError).to.be.false;
expect(sync.state).to.equal(sync.SYNC_DISCONNECTED);
expect(everSeenSyncing).to.equal(sync.SYNC_SYNCING);
expect(everSeenCompleted).to.equal(sync.SYNC_CONNECTED);
// Make sure client fs is in the same state we left it
util.ensureFilesystem(fs, layout, done);
});
expect(sync.state).to.equal(sync.SYNC_DISCONNECTED);
sync.connect(util.socketURL, token);
expect(sync.state).to.equal(sync.SYNC_CONNECTING);
});
});
});
/**
* This describe block tests downstream syncing to make sure that
* the client will restart the process in case the server errors.
* It stubs the MakeDrive server so we can control the messages being
* sent to the client.
*/
describe('Protocol & Error tests', function() {
var provider;
var socket;
var port = 1212;
var testServer;
beforeEach(function(done) {
util.ready(function() {
provider = new Filer.FileSystem.providers.Memory(util.username());
testServer = new WebSocketServer({port: port});
testServer.once('error', function(err){
expect(err, "[Error creating socket server]").to.not.exist;
});
testServer.once('listening', function() {
done();
});
});
});
afterEach(function() {
provider = null;
if (socket) {
socket.close();
}
testServer.close();
testServer = null;
});
function endTestSession(sync, done) {
sync.once('disconnected', function() {
sync = null;
done();
});
sync.disconnect();
}
function parseMessage(msg) {
msg = msg.data || msg;
msg = JSON.parse(msg);
msg = SyncMessage.parse(msg);
return msg;
}
it('should restart a downstream sync on receiving a CHKSUM ERROR SyncMessage instead of a sourceList.', function(done){
function clientLogic() {
var fs = MakeDrive.fs({provider: provider, manual: true, forceCreate: true, autoReconnect: false});
var sync = fs.sync;
sync.on('error', function(err) {
// Confirm our client-side error is emitted as expected
expect(err).to.deep.equal(new Error('Could not sync filesystem from server'));
});
sync.connect("ws://127.0.0.1:" + port, "this-is-not-relevant");
}
// First, prepare the stub of the server.
testServer.on('connection', function(ws){
socket = ws;
// Stub WS auth
ws.once('message', function(msg) {
msg = msg.data || msg;
try {
msg = JSON.parse(msg);
} catch(e) {
expect(e, "[Error parsing fake token]").to.not.exist;
}
ws.once('message', function(msg){
msg = parseMessage(msg);
expect(msg).to.deep.equal(SyncMessage.response.authz);
ws.once('message', function(msg) {
// The next message from the client should be a RESPONSE RESET
msg = parseMessage(msg);
expect(msg).to.deep.equal(SyncMessage.response.reset);
done();
});
ws.send(SyncMessage.error.srclist.stringify());
});
ws.send(SyncMessage.response.authz.stringify());
});
});
clientLogic();
});
it('should restart a downstream sync on receiving a DIFFS ERROR SyncMessage instead of a sourceList.', function(done){
var fs;
var sync;
function clientLogic() {
fs = MakeDrive.fs({provider: provider, manual: true, forceCreate: true, autoReconnect: false});
sync = fs.sync;
sync.on('error', function(err) {
// Confirm our client-side error is emitted as expected
expect(err).to.deep.equal(new Error('Could not sync filesystem from server'));
});
sync.connect("ws://127.0.0.1:" + port, "this-is-not-relevant");
}
// First, prepare the stub of the server.
testServer.on('connection', function(ws){
socket = ws;
// Stub WS auth
ws.once('message', function(msg) {
msg = msg.data || msg;
msg = parseMessage(msg);
ws.once('message', function(msg){
msg = parseMessage(msg);
expect(msg).to.deep.equal(SyncMessage.response.authz);
ws.once('message', function(msg) {
// The second message from the client should be a REQUEST DIFFS
msg = parseMessage(msg);
expect(msg.type).to.equal(SyncMessage.REQUEST);
expect(msg.name).to.equal(SyncMessage.DIFFS);
ws.once('message', function(msg) {
// The third message should be a RESPONSE RESET
msg = parseMessage(msg);
expect(msg).to.deep.equal(SyncMessage.response.reset);
done();
});
var diffsErrorMessage = SyncMessage.error.diffs;
ws.send(diffsErrorMessage.stringify());
});
rsync.sourceList(fs, '/', rsyncOptions, function(err, srcList) {
expect(err, "[SourceList generation error]").to.not.exist;
var chksumRequest = SyncMessage.request.chksum;
chksumRequest.content = {
srcList: srcList,
path: '/'
};
ws.send(chksumRequest.stringify());
});
});
ws.send(SyncMessage.response.authz.stringify());
});
});
clientLogic();
});
it('should reset a downstream sync on failing to patch', function(done){
var fs;
var sync;
function clientLogic() {
fs = MakeDrive.fs({provider: provider, manual: true, forceCreate: true, autoReconnect: false});
sync = fs.sync;
sync.on('error', function(err) {
// Confirm our client-side error is emitted as expected
expect(err).to.exist;
});
sync.connect("ws://127.0.0.1:" + port);
}
// First, prepare the stub of the server.
testServer.on('connection', function(ws){
socket = ws;
// Stub WS auth
ws.once('message', function(msg) {
msg = msg.data || msg;
msg = parseMessage(msg);
// after auth
ws.once('message', function(msg){
msg = parseMessage(msg);
expect(msg).to.deep.equal(SyncMessage.response.authz);
ws.once('message', function(msg) {
// The second message from the client should be a REQUEST DIFFS
msg = parseMessage(msg);
expect(msg.type).to.equal(SyncMessage.REQUEST);
expect(msg.name).to.equal(SyncMessage.DIFFS);
var message = SyncMessage.response.diffs;
message.content = {};
message.content.diffs = [];
message.content.diffs[0] = {};
message.content.diffs[0].diffs = [ { data: [ 102, 117, 110 ] } ];
ws.once('message', function(msg) {
// The third message should be a RESPONSE RESET
msg = parseMessage(msg);
expect(msg).to.deep.equal(SyncMessage.response.reset);
endTestSession(sync, done);
});
ws.send(message.stringify());
});
rsync.sourceList(fs, '/', rsyncOptions, function(err, srcList) {
expect(err, "[SourceList generation error]").to.not.exist;
var chksumRequest = SyncMessage.request.chksum;
chksumRequest.content = {
srcList: srcList,
path: '/'
};
ws.send(chksumRequest.stringify());
});
});
ws.send(SyncMessage.response.authz.stringify());
});
});
clientLogic();
});
it('should restart a downstream sync on receiving a verification error', function(done){
var fs;
var sync;
function clientLogic() {
fs = MakeDrive.fs({provider: provider, manual: true, forceCreate: true, autoReconnect: false});
sync = fs.sync;
sync.on('error', function(err) {
// Confirm our client-side error is emitted as expected
expect(err).to.exist;
});
sync.connect("ws://127.0.0.1:" + port);
}
// First, prepare the stub of the server.
testServer.on('connection', function(ws){
socket = ws;
// Stub WS auth
ws.once('message', function(msg) {
msg = msg.data || msg;
msg = parseMessage(msg);
// after auth
ws.once('message', function(msg){
msg = parseMessage(msg);
expect(msg).to.deep.equal(SyncMessage.response.authz);
ws.once('message', function(msg) {
// The second message from the client should be a REQUEST DIFFS
msg = parseMessage(msg);
expect(msg.type).to.equal(SyncMessage.REQUEST);
expect(msg.name).to.equal(SyncMessage.DIFFS);
var message = SyncMessage.response.diffs;
message.content = {};
message.content.diffs = [];
message.content.diffs[0] = {path: '/'};
message.content.diffs[0].diffs = [];
message.content.diffs[1] = {path: '/file.txt'};
message.content.diffs[1].diffs = [ { data: [ 102, 117, 110 ] } ];
ws.once('message', function(msg) {
// The third message should be a RESPONSE RESET
ws.once('message', function(msg) {
msg = parseMessage(msg);
expect(msg).to.deep.equal(SyncMessage.response.reset);
endTestSession(sync, done);
});
msg = parseMessage(msg);
ws.send(SyncMessage.error.verification.stringify());
});
ws.send(message.stringify());
});
rsync.sourceList(fs, '/', rsyncOptions, function(err, srcList) {
expect(err, "[SourceList generation error]").to.not.exist;
var chksumRequest = SyncMessage.request.chksum;
chksumRequest.content = {
srcList: srcList,
path: '/'
};
ws.send(chksumRequest.stringify());
});
});
ws.send(SyncMessage.response.authz.stringify());
});
});
clientLogic();
});
it('should emit an error describing an incorrect SYNC_STATE in the sync.request step', function(done){
util.authenticatedConnection(function( err, result ) {
expect(err).not.to.exist;
var token = result.token;
var layout = {'/file': 'data'};
var fs;
var sync;
fs = MakeDrive.fs({provider: provider, manual: true, forceCreate: true, autoReconnect: false});
sync = fs.sync;
sync.once('connected', function onConnected() {
sync.once('disconnected', function onDisconnected() {
sync.once('error', function(err){
expect(err).to.exist;
expect(err).to.deep.equal(new Error('Invalid state. Expected ' + sync.SYNC_CONNECTED + ', got ' + sync.SYNC_DISCONNECTED));
done();
});
sync.request();
});
// Make FS changes and try to sync
util.createFilesystemLayout(fs, layout, function(err) {
expect(err).not.to.exist;
sync.disconnect();
});
});
sync.connect(util.socketURL, token);
});
});
it('should emit an error warning about an unexpected sync.state when calling the sync.auto step', function(done){
util.authenticatedConnection(function( err, result ) {
expect(err).not.to.exist;
var token = result.token;
var layout = {'/file': 'data'};
var fs;
var sync;
fs = MakeDrive.fs({provider: provider, manual: true, forceCreate: true, autoReconnect: false});
sync = fs.sync;
sync.once('connected', function onConnected() {
sync.once('disconnected', function onDisconnected() {
sync.once('error', function(err){
expect(err).to.exist;
expect(err).to.deep.equal(new Error('Invalid state. ' + sync.state + ' is unexpected.'));
done();
});
sync.auto();
});
// Make FS changes and try to sync
util.createFilesystemLayout(fs, layout, function(err) {
expect(err).not.to.exist;
sync.disconnect();
});
});
sync.connect(util.socketURL, token);
});
});
it('should emit an error describing an already CONNECTED sync.state in the sync.connect step', function(done){
util.authenticatedConnection(function( err, result ) {
expect(err).not.to.exist;
var token = result.token;
var fs;
var sync;
fs = MakeDrive.fs({provider: provider, manual: true, forceCreate: true, autoReconnect: false});
sync = fs.sync;
sync.once('connected', function onConnected() {
sync.once('error', function(err){
expect(err).to.exist;
expect(err).to.deep.equal(new Error("MakeDrive: Attempted to connect to the websocket URL, but a connection already exists!"));
done();
});
sync.connect(util.socketURL, token);
});
sync.connect(util.socketURL, token);
});
});
it('should emit an error on an attempted sync.disconnect() call when already DISCONNECTED', function(done){
util.authenticatedConnection(function( err, result ) {
expect(err).not.to.exist;
var token = result.token;
var fs;
var sync;
fs = MakeDrive.fs({provider: provider, manual: true, forceCreate: true, autoReconnect: false});
sync = fs.sync;
sync.once('connected', function onConnected() {
sync.once('disconnected', function onDisconnected() {
sync.once('error', function(err){
expect(err).to.exist;
expect(err).to.deep.equal(new Error("MakeDrive: Attempted to disconnect, but no server connection exists!"));
done();
});
sync.disconnect();
});
sync.disconnect();
});
sync.connect(util.socketURL, token);
});
});
});
});