diff --git a/src/jio.storage/replicatestorage.js b/src/jio.storage/replicatestorage.js index abde9d2340cca0d592aaeec626bd07c54c775b67..a5846def31930802419eea220d3934255b9da7ca 100644 --- a/src/jio.storage/replicatestorage.js +++ b/src/jio.storage/replicatestorage.js @@ -189,7 +189,104 @@ }); } - function checkLocalDeletion(queue, destination, id, source) { + function propagateDeletion(destination, id) { + return destination.remove(id) + .push(function () { + return context._signature_sub_storage.remove(id); + }) + .push(function () { + skip_document_dict[id] = null; + }); + } + + 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; + }); + } + + 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 () { @@ -197,32 +294,11 @@ }) .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) { - return destination.remove(id) - .push(function () { - return context._signature_sub_storage.remove(id); - }) - .push(function () { - skip_document_dict[id] = null; - }); - } - // Modifications on remote side - // Push them locally - return propagateModification(destination, source, doc, - remote_hash, id); - }, function (error) { - if ((error instanceof jIO.util.jIOError) && - (error.status_code === 404)) { - return context._signature_sub_storage.remove(id) - .push(function () { - skip_document_dict[id] = null; - }); - } - throw error; - }); + 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 === diff --git a/test/jio.storage/replicatestorage.tests.js b/test/jio.storage/replicatestorage.tests.js index 5596e38e2072e4b4234b74fdf63b07d6736eea1a..8e45d87dfb0d217200f11648cb1431fe65f22be8 100644 --- a/test/jio.storage/replicatestorage.tests.js +++ b/test/jio.storage/replicatestorage.tests.js @@ -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);