c9-core/plugins/c9.ide.collab/server/collab-server_test.js

489 wiersze
17 KiB
JavaScript

"use strict";
"use server";
require("c9/inline-mocha")(module, null, { globals: ["db", "columnTypes"]});
var assert = require("assert");
var async = require("async");
var vfsLocal = require("vfs-local");
var fsnode = require("vfs-nodefs-adapter");
var fs = require("fs");
var vfsCollab = require("./collab-server");
var execFile = require("child_process").execFile;
var path = require("path");
var faker = require("faker");
var TEST_PID = 800;
var user1 = {
user: {
uid: "123",
fullname: "Mostafa Eweda",
email: "mostafa@c9.io"
},
clientId: "123abc",
readonly: false
};
var user2 = {
user: {
uid: "456",
fullname: "Maged Eweda",
email: "maged@c9.io",
},
clientId: "456abc",
readonly: false
};
var user3 = {
user: {
uid: "789",
fullname: "readonly user",
email: "readonly@c9.io",
},
clientId: "789xyz",
readonly: true
};
function initCollab(user, next) {
var vfs = vfsLocal({
root: "/",
wsmetapath: ".metadata",
nodePath: __dirname + "/../../../node_modules",
nopty: true
});
vfs.extend("collab", {
file: __dirname + "/collab-server.js",
redefine: true,
user: user.user,
project: { pid: TEST_PID },
readonly: user.readonly
}, function (err, meta) {
if (err || !meta || !meta.api)
assert.equal(err, null);
assert.ok(meta);
assert.ok(meta.api);
var collab = meta.api;
collab.connect({
basePath: __dirname,
clientId: user.clientId
}, function (err, meta) {
assert.equal(err, null);
assert.ok(meta);
assert.ok(meta.stream);
collab.stream = meta.stream;
collab.user = user;
collab.meta = meta;
setTimeout(function () {
next(null, collab, vfs);
}, 100);
});
});
}
describe(__filename, function() {
this.timeout(15000);
before(function (next) {
execFile("rm", ["-rf", path.join(process.env.HOME, "/.c9/" + TEST_PID)], function(code, stdout, stderr) {
if (!code)
return next();
next(stderr);
});
});
describe("General Collab", function() {
after(function(next) {
fs.unlinkSync(__dirname + "/~test.txt");
next();
//module.exports.setUpSuite(next);
});
beforeEach(function(next) {
var _self = this;
initCollab(user1, function (err, collab1, vfs) {
if (err)
return next(err);
_self.collab1 = collab1;
_self.vfs = vfs;
_self.fs = fsnode(vfs);
initCollab(user2, function (err, collab2) {
if (err)
return next(err);
_self.collab2 = collab2;
var path = "~test.txt";
var text = 'abc-def;;\nghi"-"jkl\n';
fs.writeFileSync(__dirname + "/" + path, text);
vfsCollab.Store.newDocument({
path: path,
contents: text
}, function() {
next();
});
});
});
});
afterEach(function (next) {
var _self = this;
this.collab2 && this.collab2.dispose(user2.clientId);
setTimeout(function () {
_self.collab1 && _self.collab1.dispose(user1.clientId);
setTimeout(next, 100);
}, 100);
});
it("should 2 clients collab initialization", function() {
var collab1 = this.collab1;
var collab2 = this.collab2;
assert.ok(collab1);
assert.ok(collab2);
assert.ok(collab1.meta.isMaster);
assert.ok(!collab2.meta.isMaster);
});
it("should broadcasting server", function(next) {
this.collab1.stream.once("data", function (data) {
console.log("Stream data:", data.toString());
next();
});
this.collab1.send(user1.clientId, { type: "PING" });
});
it("should stream end on dispose", function(next) {
this.collab1.stream.once("end", function (data) {
next();
});
this.collab1.dispose(user1.clientId);
});
function joinDocument(docPath, toJoin, otherCollab, next) {
var initatorMsg, collabMsg;
var joinerStream = toJoin.stream;
var collabStream = otherCollab.stream;
async.parallel([
function (next) {
joinerStream.on("data", function collab2Stream(msg) {
msg = JSON.parse(msg);
if (msg.type !== "JOIN_DOC")
return console.log("unexpected message:", msg);
assert.equal(msg.type, "JOIN_DOC");
joinerStream.removeListener("data", collab2Stream);
initatorMsg = msg.data;
next();
});
},
function (next) {
collabStream.on("data", function collab1Stream(msg) {
msg = JSON.parse(msg);
if (msg.type !== "JOIN_DOC")
return console.log("unexpected message:", msg);
assert.equal(msg.type, "JOIN_DOC");
collabStream.removeListener("data", collab1Stream);
collabMsg = msg.data;
next();
});
}
], function (err) {
if (err)
return next(err);
assert(initatorMsg);
assert(collabMsg);
assert.equal(initatorMsg.docId, docPath.replace(/^\//, ""));
assert(initatorMsg.chunk);
var doc = JSON.parse(initatorMsg.chunk);
assert.ok(!collabMsg.doc);
next(null, initatorMsg.docId);
});
toJoin.send(toJoin.user.clientId, {
type: "JOIN_DOC",
data: { docId: docPath }
});
}
it("should join document from master", function(next) {
joinDocument("~test.txt", this.collab1, this.collab2, next);
});
it("should join document from slave", function(next) {
joinDocument("~test.txt", this.collab2, this.collab1, next);
});
xit("should leave document", function (next) {
var _self = this;
var docPath = "~test.txt";
joinDocument(docPath, _self.collab1, _self.collab2, function (err) {
assert.ok(!err);
joinDocument(docPath, _self.collab2, _self.collab1, function (err) {
assert.ok(!err);
var numLeaves = 2;
function assertLeaveMsg(next, msg) {
if (!numLeaves)
return;
numLeaves--;
msg = JSON.parse(msg);
assert.equal(msg.type, "LEAVE_DOC");
var data = msg.data;
assert.equal(msg.data.clientId, collabClientId);
next();
}
async.parallel([
function (next) {
_self.collab1.stream.on("data", assertLeaveMsg.bind(null, next));
},
function (next) {
_self.collab2.stream.on("data", assertLeaveMsg.bind(null, next));
}
], next);
var collab = _self.collab1;
var collabClientId = collab.userIds.clientId;
// Anyone can leave -- everybody notified
collab.send(collabClientId, {
type: "LEAVE_DOC",
data: { docId: docPath }
});
});
});
});
it("should editing document - sync commit error", function(next) {
var _self = this;
var docPath = "~test.txt";
joinDocument(docPath, _self.collab2, _self.collab1, function (err) {
assert.ok(!err);
_self.collab2.stream.on("data", function (msg) {
msg = JSON.parse(msg);
assert.equal(msg.type, "SYNC_COMMIT");
assert.equal(msg.data.docId, docPath);
assert.equal(msg.data.revNum, 0);
next();
});
_self.collab2.send(user2.clientId, {
type: "EDIT_UPDATE",
data: {
docId: docPath,
revNum: 2, // commit with wrong revision number ( != 1 )
op: ["r1", "ik", "r209"]
}
});
});
});
it("should editing document - a single commit", function(next) {
var _self = this;
var docPath = "~test.txt";
joinDocument(docPath, _self.collab1, _self.collab2, function (err) {
assert.ok(!err);
joinDocument(docPath, _self.collab2, _self.collab1, function (err) {
assert.ok(!err);
// will be received by both joined collab streams
_self.collab2.stream.on("data", function (msg) {
msg = JSON.parse(msg);
assert.equal(msg.type, firstEditMsg.type);
assert.equal(msg.data.docId, firstEditMsg.data.docId);
assert.equal(msg.data.revNum, firstEditMsg.data.revNum);
assert.deepEqual(msg.data.op, firstEditMsg.data.op);
next();
});
var firstEditMsg = {
type: "EDIT_UPDATE",
data: {
docId: docPath,
revNum: 1,
op: ["r1", "ik", "r209"]
}
};
// Anyone can leave -- everybody notified
_self.collab1.send(user1.clientId, firstEditMsg);
});
});
});
it("should the master leaving and re-connecting", function(next) {
var _self = this;
this.collab1.dispose(user1.clientId);
setTimeout(function () {
initCollab(user1, function(err, collab1) {
assert.ok(!err);
_self.collab1 = collab1;
next();
});
}, 1000);
});
it("should a participant leaving and re-connecting", function(next) {
var _self = this;
this.collab2.dispose(user2.clientId);
initCollab(user2, function(err, collab2) {
assert.ok(!err);
_self.collab2 = collab2;
next();
});
});
it("should block home access for readonly", function(next) {
var self = this;
function testJoinError(collab, path, next) {
collab.stream.on("data", function onData(msg) {
msg = JSON.parse(msg);
if (msg.type !== "JOIN_DOC")
return console.log("unexpected message:", msg);
assert.equal(msg.data.err.message, "Not allowed.");
assert.ok(!msg.data.chunk);
collab.stream.removeListener("data", onData);
next();
});
collab.send(collab.user.clientId, {
type: "JOIN_DOC",
data: { docId: path }
});
}
async.series([
function(next) {
joinDocument("/~test.txt", self.collab1, self.collab2, function(err, id) {
assert.ok(!err);
assert.equal(id, "~test.txt");
next();
});
},
function(next) {
testJoinError(self.collab1, "a/b/../../../~test.txt", next);
},
function(next) {
testJoinError(self.collab1, "../~test.txt", next);
},
function(next) {
testJoinError(self.collab1, "..\\..\\test.txt", next);
},
function(next) {
initCollab(user3, function (err, collabRO, vfs) {
testJoinError(collabRO, "~/test.txt", next);
});
},
function() {
next();
}
]);
});
});
describe("checkDBCorruption", function() {
it("Should return eithout error if duplicate column name error is encountered", function(done) {
var err = new Error("SQLITE_ERROR: duplicate column name: migration");
vfsCollab.checkDBCorruption(err, function (err, result) {
assert(!err);
assert(!result);
done();
});
});
});
describe("areOperationsMirrored", function() {
it("Should return false for unmirrored operations", function() {
var op1 = ["r54", "dabc"];
var op2 = ["r44", "dabc"];
assert.equal(vfsCollab.areOperationsMirrored(op1, op2), false);
});
it("Should return true for mirrored operations", function() {
var op1 = ["r54", "dabc"];
var op2 = ["r54", "iabc"];
assert.equal(vfsCollab.areOperationsMirrored(op1, op2), true);
});
it("Should return false for the same operation", function() {
var op1 = ["r54", "iaaa"];
var op2 = ["r54", "iaaa"];
assert.equal(vfsCollab.areOperationsMirrored(op1, op2), false);
});
it("Should return false if the operations are different lengths", function() {
var op1 = ["r54", "iaaa", "ibbb"];
var op2 = ["r54", "iaaa"];
assert.equal(vfsCollab.areOperationsMirrored(op1, op2), false);
op1 = ["r54", "iaaa"];
op2 = ["r54", "iaaa", "ibbb"];
assert.equal(vfsCollab.areOperationsMirrored(op1, op2), false);
});
it("Should be able to handle empty or invalid operations without crashing", function() {
var op1 = ["r55"];
var op2 = [];
assert.equal(vfsCollab.areOperationsMirrored(op1, op2), false);
op1 = [""];
op2 = [];
assert.equal(vfsCollab.areOperationsMirrored(op1, op2), false);
op1 = [];
op2 = ["r44", ""];
assert.equal(vfsCollab.areOperationsMirrored(op1, op2), false);
});
it("Should ignore noop operations", function() {
var op1 = ["d ", "r0", "i"];
var op2 = ["d", "r0", "i "];
assert.equal(vfsCollab.areOperationsMirrored(op1, op2), true);
});
it("Should work when change orders are switched", function() {
var op1 = ["dELEMENT", "iSTORIES"];
var op2 = ["dSTORIES", "iELEMENT"];
assert.equal(vfsCollab.areOperationsMirrored(op1, op2), true);
});
it("Should not modify the operations passed in", function() {
var op1 = ["r5", "i", "imew"];
var op2 = ["dlll", "r0"];
vfsCollab.areOperationsMirrored(op1, op2);
assert.deepEqual(op1, ["r5", "i", "imew"]);
assert.deepEqual(op2, ["dlll", "r0"]);
});
});
describe("removeNoopOperations", function () {
it("Should remove operations that do nothing", function() {
var operations = ["i", "dUUU", "r0", "r1", "r0", "d", "i5e"];
var operationsParsed = vfsCollab.removeNoopOperations(operations);
assert.deepEqual(operationsParsed, ["dUUU", "r1", "i5e"]);
});
it("Should not modify the operations passed in", function() {
var op1 = ["r544", "d", "iaaa"];
vfsCollab.removeNoopOperations(op1);
assert.deepEqual(op1, ["r544", "d", "iaaa"]);
});
});
});