Commit d177be73 authored by Romain Courteaud's avatar Romain Courteaud

ReplicateStorage: respect configuration in all cases

parent ab653402
......@@ -189,18 +189,7 @@
});
}
function checkLocalDeletion(queue, destination, id, source) {
var status_hash;
queue
.push(function () {
return context._signature_sub_storage.get(id);
})
.push(function (result) {
status_hash = result.hash;
return destination.get(id)
.push(function (doc) {
var remote_hash = generateHash(stringify(doc));
if (remote_hash === status_hash) {
function propagateDeletion(destination, id) {
return destination.remove(id)
.push(function () {
return context._signature_sub_storage.remove(id);
......@@ -209,20 +198,107 @@
skip_document_dict[id] = null;
});
}
// Modifications on remote side
// Push them locally
return propagateModification(destination, source, doc,
remote_hash, id);
function checkAndPropagate(status_hash, local_hash, doc,
source, destination, id,
conflict_force, conflict_revert,
conflict_ignore,
options) {
return destination.get(id)
.push(function (remote_doc) {
return [remote_doc, generateHash(stringify(remote_doc))];
}, function (error) {
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
return [null, null];
}
throw error;
})
.push(function (remote_list) {
var remote_doc = remote_list[0],
remote_hash = remote_list[1];
if (local_hash === remote_hash) {
// Same modifications on both side
if (local_hash === null) {
// Deleted on both side, drop signature
return context._signature_sub_storage.remove(id)
.push(function () {
skip_document_dict[id] = null;
});
}
throw error;
return context._signature_sub_storage.put(id, {
"hash": local_hash
})
.push(function () {
skip_document_dict[id] = null;
});
}
if ((remote_hash === status_hash) || (conflict_force === true)) {
// Modified only locally. No conflict or force
if (local_hash === null) {
// Deleted locally
return propagateDeletion(destination, id);
}
return propagateModification(source, destination, doc,
local_hash, id,
{use_post: ((options.use_post) &&
(remote_hash === null))});
}
// Conflict cases
if (conflict_ignore === true) {
return;
}
if ((conflict_revert === true) || (local_hash === null)) {
// Automatically resolve conflict or force revert
if (remote_hash === null) {
// Deleted remotely
return propagateDeletion(source, id);
}
return propagateModification(
destination,
source,
remote_doc,
remote_hash,
id,
{use_post: ((options.use_revert_post) &&
(local_hash === null))}
);
}
// Minimize conflict if it can be resolved
if (remote_hash === null) {
// Copy remote modification remotely
return propagateModification(source, destination, doc,
local_hash, id,
{use_post: options.use_post});
}
throw new jIO.util.jIOError("Conflict on '" + id + "': " +
stringify(doc || '') + " !== " +
stringify(remote_doc || ''),
409);
});
}
function checkLocalDeletion(queue, destination, id, source,
conflict_force, conflict_revert,
conflict_ignore, options) {
var status_hash;
queue
.push(function () {
return context._signature_sub_storage.get(id);
})
.push(function (result) {
status_hash = result.hash;
return checkAndPropagate(status_hash, null, null,
source, destination, id,
conflict_force, conflict_revert,
conflict_ignore,
options);
});
}
......@@ -237,7 +313,7 @@
if (is_creation === true) {
return RSVP.all([
getMethod(id),
{hash: undefined}
{hash: null}
]);
}
if (is_modification === true) {
......@@ -256,57 +332,11 @@
status_hash = result_list[1].hash;
if (local_hash !== status_hash) {
// Local modifications
return destination.get(id)
.push(function (remote_doc) {
var remote_hash = generateHash(stringify(remote_doc));
if (remote_hash !== status_hash) {
// Modifications on both sides
if (local_hash === remote_hash) {
// Same modifications on both side \o/
return context._signature_sub_storage.put(id, {
"hash": local_hash
})
.push(function () {
skip_document_dict[id] = null;
});
}
if (conflict_ignore === true) {
return;
}
if (conflict_revert === true) {
return propagateModification(destination, source,
remote_doc,
remote_hash, id);
}
if (conflict_force === false) {
throw new jIO.util.jIOError("Conflict on '" + id + "': " +
stringify(doc) + " !== " +
stringify(remote_doc),
409);
}
}
return propagateModification(source, destination, doc,
local_hash, id);
}, function (error) {
var use_post;
if ((error instanceof jIO.util.jIOError) &&
(error.status_code === 404)) {
if (is_creation) {
// Remote document does not exists, create it following
// provided options
use_post = options.use_post;
} else {
// Remote document has been erased, put it to save
// modification
use_post = false;
}
return propagateModification(source, destination, doc,
local_hash, id,
{use_post: use_post});
}
throw error;
});
return checkAndPropagate(status_hash, local_hash, doc,
source, destination, id,
conflict_force, conflict_revert,
conflict_ignore,
options);
}
});
}
......@@ -350,6 +380,9 @@
if (!options.hasOwnProperty("use_post")) {
options.use_post = false;
}
if (!options.hasOwnProperty("use_revert_post")) {
options.use_revert_post = false;
}
return queue
.push(function () {
return RSVP.all([
......@@ -412,7 +445,11 @@
for (key in signature_dict) {
if (signature_dict.hasOwnProperty(key)) {
if (!local_dict.hasOwnProperty(key)) {
checkLocalDeletion(queue, destination, key, source);
checkLocalDeletion(queue, destination, key, source,
options.conflict_force,
options.conflict_revert,
options.conflict_ignore,
options);
}
}
}
......@@ -502,6 +539,7 @@
return pushStorage(context._remote_sub_storage,
context._local_sub_storage, {
use_bulk_get: use_bulk_get,
use_revert_post: context._use_remote_post,
conflict_force: (context._conflict_handling ===
CONFLICT_KEEP_REMOTE),
conflict_revert: (context._conflict_handling ===
......
......@@ -1647,6 +1647,66 @@
});
});
test("local document modification: use remote post", function () {
stop();
expect(2);
var id,
context = this;
this.jio = jIO.createJIO({
type: "replicate",
use_remote_post: true,
local_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
},
remote_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
context.jio.__storage._remote_sub_storage.post({"title": "foo"})
.then(function (result) {
id = result;
return context.jio.repair();
})
.then(function () {
return context.jio.put(id, {"title": "foo2"});
})
.then(function () {
return context.jio.repair();
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
.then(function (result) {
deepEqual(result, {
title: "foo2"
});
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(id);
})
.then(function (result) {
deepEqual(result, {
hash: "9819187e39531fdc9bcfd40dbc6a7d3c78fe8dab"
});
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("local document modification not checked", function () {
stop();
expect(3);
......@@ -2450,13 +2510,30 @@
});
});
test("local modifications and remote deletion", function () {
test("local deletion and remote modifications: keep local", function () {
stop();
expect(2);
expect(9);
var id,
context = this;
this.jio = jIO.createJIO({
type: "replicate",
conflict_handling: 1,
local_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
},
remote_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
context.jio.post({"title": "foo"})
.then(function (result) {
id = result;
......@@ -2464,13 +2541,98 @@
})
.then(function () {
return RSVP.all([
context.jio.put(id, {"title": "foo99"}),
context.jio.__storage._remote_sub_storage.remove(id)
context.jio.remove(id),
context.jio.__storage._remote_sub_storage.put(id, {"title": "foo99"})
]);
})
.then(function () {
return context.jio.repair();
})
.then(function () {
return context.jio.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(
error.message.indexOf(
"Cannot find attachment: " +
"_replicate_b9296354cdf1dbe0046de11f57a5a24f8f6a78a8 , " +
"jio_document/"
),
0
);
equal(error.status_code, 404);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("local deletion and remote modifications: keep remote", function () {
stop();
expect(3);
var id,
context = this;
this.jio = jIO.createJIO({
type: "replicate",
conflict_handling: 2,
local_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
},
remote_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
context.jio.post({"title": "foo"})
.then(function (result) {
id = result;
return context.jio.repair();
})
.then(function () {
return RSVP.all([
context.jio.remove(id),
context.jio.__storage._remote_sub_storage.put(id, {"title": "foo99"})
]);
})
.then(function () {
return context.jio.repair();
})
.then(function () {
return context.jio.get(id);
})
.then(function (result) {
deepEqual(result, {
title: "foo99"
});
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
......@@ -2495,6 +2657,750 @@
});
});
test("local deletion and remote modifications: ignore", function () {
stop();
expect(5);
var id,
context = this;
this.jio = jIO.createJIO({
type: "replicate",
conflict_handling: 3,
local_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
},
remote_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
context.jio.post({"title": "foo"})
.then(function (result) {
id = result;
return context.jio.repair();
})
.then(function () {
return RSVP.all([
context.jio.remove(id),
context.jio.__storage._remote_sub_storage.put(id, {"title": "foo99"})
]);
})
.then(function () {
return context.jio.repair();
})
.then(function () {
return context.jio.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
.then(function (result) {
deepEqual(result, {
title: "foo99"
});
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(id);
})
.then(function (result) {
deepEqual(result, {
hash: "5ea9013447539ad65de308cbd75b5826a2ae30e5"
});
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("local modifications and remote deletion", function () {
stop();
expect(2);
var id,
context = this;
context.jio.post({"title": "foo"})
.then(function (result) {
id = result;
return context.jio.repair();
})
.then(function () {
return RSVP.all([
context.jio.put(id, {"title": "foo99"}),
context.jio.__storage._remote_sub_storage.remove(id)
]);
})
.then(function () {
return context.jio.repair();
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
.then(function (result) {
deepEqual(result, {
title: "foo99"
});
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(id);
})
.then(function (result) {
deepEqual(result, {
hash: "8ed3a474128b6e0c0c7d3dd51b1a06ebfbf6722f"
});
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("local modifications and remote deletion: use remote post", function () {
stop();
expect(13);
var id,
context = this,
post_id;
this.jio = jIO.createJIO({
type: "replicate",
use_remote_post: true,
local_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
},
remote_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
context.jio.__storage._remote_sub_storage.post({"title": "foo"})
.then(function (result) {
id = result;
return context.jio.repair();
})
.then(function () {
return RSVP.all([
context.jio.put(id, {"title": "foo99"}),
context.jio.__storage._remote_sub_storage.remove(id)
]);
})
.then(function () {
return context.jio.repair();
})
// Old id deleted
.then(function () {
return context.jio.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(
error.message.indexOf(
"Cannot find attachment: " +
"_replicate_b9296354cdf1dbe0046de11f57a5a24f8f6a78a8 , " +
"jio_document/"
),
0
);
equal(error.status_code, 404);
})
// Check new id
.then(function () {
return context.jio.allDocs({include_docs: true});
})
.then(function (result) {
equal(result.data.total_rows, 1);
post_id = result.data.rows[0].id;
return context.jio.__storage._remote_sub_storage.get(post_id);
})
.then(function (result) {
deepEqual(result, {
"title": "foo99"
});
})
.then(function () {
return context.jio.__storage._local_sub_storage.get(post_id);
})
.then(function (result) {
deepEqual(result, {
"title": "foo99"
});
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(post_id);
})
.then(function (result) {
deepEqual(result, {
hash: "8ed3a474128b6e0c0c7d3dd51b1a06ebfbf6722f"
});
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("local modif, remote del: remote post, no check loc mod", function () {
stop();
expect(13);
var id,
context = this,
post_id;
this.jio = jIO.createJIO({
type: "replicate",
use_remote_post: true,
check_local_modification: false,
local_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
},
remote_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
context.jio.__storage._remote_sub_storage.post({"title": "foo"})
.then(function (result) {
id = result;
return context.jio.repair();
})
.then(function () {
return RSVP.all([
context.jio.put(id, {"title": "foo99"}),
context.jio.__storage._remote_sub_storage.remove(id)
]);
})
.then(function () {
return context.jio.repair();
})
// Old id deleted
.then(function () {
return context.jio.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(
error.message.indexOf(
"Cannot find attachment: " +
"_replicate_b9296354cdf1dbe0046de11f57a5a24f8f6a78a8 , " +
"jio_document/"
),
0
);
equal(error.status_code, 404);
})
// Check new id
.then(function () {
return context.jio.allDocs({include_docs: true});
})
.then(function (result) {
equal(result.data.total_rows, 1);
post_id = result.data.rows[0].id;
return context.jio.__storage._remote_sub_storage.get(post_id);
})
.then(function (result) {
deepEqual(result, {
"title": "foo99"
});
})
.then(function () {
return context.jio.__storage._local_sub_storage.get(post_id);
})
.then(function (result) {
deepEqual(result, {
"title": "foo99"
});
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(post_id);
})
.then(function (result) {
deepEqual(result, {
hash: "8ed3a474128b6e0c0c7d3dd51b1a06ebfbf6722f"
});
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("local modifications and remote deletion: keep local", function () {
stop();
expect(3);
var id,
context = this;
this.jio = jIO.createJIO({
type: "replicate",
conflict_handling: 1,
local_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
},
remote_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
context.jio.post({"title": "foo"})
.then(function (result) {
id = result;
return context.jio.repair();
})
.then(function () {
return RSVP.all([
context.jio.put(id, {"title": "foo99"}),
context.jio.__storage._remote_sub_storage.remove(id)
]);
})
.then(function () {
return context.jio.repair();
})
// Old id deleted
.then(function () {
return context.jio.get(id);
})
.then(function (result) {
deepEqual(result, {
"title": "foo99"
});
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
.then(function (result) {
deepEqual(result, {
"title": "foo99"
});
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(id);
})
.then(function (result) {
deepEqual(result, {
hash: "8ed3a474128b6e0c0c7d3dd51b1a06ebfbf6722f"
});
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("local modif and remote del: keep local, use remote post", function () {
stop();
expect(13);
var id,
context = this,
post_id;
this.jio = jIO.createJIO({
type: "replicate",
conflict_handling: 1,
use_remote_post: true,
local_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
},
remote_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
context.jio.__storage._remote_sub_storage.post({"title": "foo"})
.then(function (result) {
id = result;
return context.jio.repair();
})
.then(function () {
return RSVP.all([
context.jio.put(id, {"title": "foo99"}),
context.jio.__storage._remote_sub_storage.remove(id)
]);
})
.then(function () {
return context.jio.repair();
})
// Old id deleted
.then(function () {
return context.jio.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(
error.message.indexOf(
"Cannot find attachment: " +
"_replicate_b9296354cdf1dbe0046de11f57a5a24f8f6a78a8 , " +
"jio_document/"
),
0
);
equal(error.status_code, 404);
})
// Check new id
.then(function () {
return context.jio.allDocs({include_docs: true});
})
.then(function (result) {
equal(result.data.total_rows, 1);
post_id = result.data.rows[0].id;
return context.jio.__storage._remote_sub_storage.get(post_id);
})
.then(function (result) {
deepEqual(result, {
"title": "foo99"
});
})
.then(function () {
return context.jio.__storage._local_sub_storage.get(post_id);
})
.then(function (result) {
deepEqual(result, {
"title": "foo99"
});
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(post_id);
})
.then(function (result) {
deepEqual(result, {
hash: "8ed3a474128b6e0c0c7d3dd51b1a06ebfbf6722f"
});
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("local modifications and remote deletion: keep remote", function () {
stop();
expect(9);
var id,
context = this;
this.jio = jIO.createJIO({
type: "replicate",
conflict_handling: 2,
local_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
},
remote_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
context.jio.post({"title": "foo"})
.then(function (result) {
id = result;
return context.jio.repair();
})
.then(function () {
return RSVP.all([
context.jio.put(id, {"title": "foo99"}),
context.jio.__storage._remote_sub_storage.remove(id)
]);
})
.then(function () {
return context.jio.repair();
})
// id deleted
.then(function () {
return context.jio.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(
error.message.indexOf(
"Cannot find attachment: " +
"_replicate_b9296354cdf1dbe0046de11f57a5a24f8f6a78a8 , " +
"jio_document/"
),
0
);
equal(error.status_code, 404);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("local modif and remote del: keep remote, not check modif", function () {
stop();
expect(9);
var id,
context = this;
this.jio = jIO.createJIO({
type: "replicate",
conflict_handling: 2,
check_local_modification: false,
local_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
},
remote_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
context.jio.post({"title": "foo"})
.then(function (result) {
id = result;
return context.jio.repair();
})
.then(function () {
return RSVP.all([
context.jio.put(id, {"title": "foo99"}),
context.jio.__storage._remote_sub_storage.remove(id)
]);
})
.then(function () {
return context.jio.repair();
})
// id deleted
.then(function () {
return context.jio.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(
error.message.indexOf(
"Cannot find attachment: " +
"_replicate_b9296354cdf1dbe0046de11f57a5a24f8f6a78a8 , " +
"jio_document/"
),
0
);
equal(error.status_code, 404);
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("local modifications and remote deletion: ignore", function () {
stop();
expect(5);
var id,
context = this;
this.jio = jIO.createJIO({
type: "replicate",
conflict_handling: 3,
local_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
},
remote_sub_storage: {
type: "uuid",
sub_storage: {
type: "memory"
}
}
});
context.jio.post({"title": "foo"})
.then(function (result) {
id = result;
return context.jio.repair();
})
.then(function () {
return RSVP.all([
context.jio.put(id, {"title": "foo99"}),
context.jio.__storage._remote_sub_storage.remove(id)
]);
})
.then(function () {
return context.jio.repair();
})
// id deleted
.then(function () {
return context.jio.get(id);
})
.then(function (result) {
deepEqual(result, {
"title": "foo99"
});
})
.then(function () {
return context.jio.__storage._remote_sub_storage.get(id);
})
.fail(function (error) {
ok(error instanceof jIO.util.jIOError);
equal(error.message, "Cannot find document: " + id);
equal(error.status_code, 404);
})
.then(function () {
return context.jio.__storage._signature_sub_storage.get(id);
})
.then(function (result) {
deepEqual(result, {
hash: "5ea9013447539ad65de308cbd75b5826a2ae30e5"
});
})
.fail(function (error) {
ok(false, error);
})
.always(function () {
start();
});
});
test("signature document is not synced", function () {
stop();
expect(6);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment