From 9007e2d21dea8b0185c0096b30364a8ee40a3867 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Fri, 13 Mar 2009 22:15:34 +0000 Subject: Commit Damien's rep_security branch to trunk. Changes bulk_docs conflict checking. Breaks file format, see mailing list for data upgrade procedure, or http://wiki.apache.org/couchdb/Breaking_changes git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@753448 13f79535-47bb-0310-9956-ffa450edef68 --- share/Makefile.am | 1 + share/www/script/couch.js | 44 ++++-- share/www/script/couch_tests.js | 1 + share/www/script/test/attachment_names.js | 2 +- share/www/script/test/attachments.js | 4 +- share/www/script/test/basics.js | 12 +- share/www/script/test/bulk_docs.js | 71 +++++++--- share/www/script/test/compact.js | 8 +- share/www/script/test/design_docs.js | 2 +- share/www/script/test/etags_views.js | 3 +- share/www/script/test/invalid_docids.js | 14 +- share/www/script/test/list_views.js | 6 +- share/www/script/test/lots_of_docs.js | 2 +- share/www/script/test/purge.js | 2 +- share/www/script/test/reduce.js | 6 +- share/www/script/test/reduce_false.js | 2 +- share/www/script/test/replication.js | 41 +++++- share/www/script/test/rev_stemming.js | 93 +++++++++++++ share/www/script/test/security_validation.js | 165 +++++++++++++++++++---- share/www/script/test/view_include_docs.js | 2 +- share/www/script/test/view_multi_key_all_docs.js | 2 +- share/www/script/test/view_multi_key_design.js | 2 +- share/www/script/test/view_multi_key_temp.js | 2 +- share/www/script/test/view_pagination.js | 2 +- 24 files changed, 393 insertions(+), 96 deletions(-) create mode 100644 share/www/script/test/rev_stemming.js (limited to 'share') diff --git a/share/Makefile.am b/share/Makefile.am index 0f8daaab..3cabe166 100644 --- a/share/Makefile.am +++ b/share/Makefile.am @@ -104,6 +104,7 @@ nobase_dist_localdata_DATA = \ www/script/test/view_sandboxing.js \ www/script/test/view_xml.js \ www/script/test/replication.js \ + www/script/test/rev_stemming.js \ www/script/test/etags_head.js \ www/script/test/etags_views.js \ www/script/test/show_documents.js \ diff --git a/share/www/script/couch.js b/share/www/script/couch.js index 821fb694..4586cca8 100644 --- a/share/www/script/couch.js +++ b/share/www/script/couch.js @@ -98,15 +98,26 @@ function CouchDB(name, httpHeaders) { if (docs[i]._id == undefined) docs[i]._id = newUuids.pop(); } - this.last_req = this.request("POST", this.uri + "_bulk_docs" + encodeOptions(options), { - body: JSON.stringify({"docs": docs}) + var json = {"docs": docs}; + // put any options in the json + for (var option in options) { + json[option] = options[option]; + } + this.last_req = this.request("POST", this.uri + "_bulk_docs", { + body: JSON.stringify(json) }); - CouchDB.maybeThrowError(this.last_req); - var result = JSON.parse(this.last_req.responseText); - for (var i = 0; i < docs.length; i++) { - docs[i]._rev = result.new_revs[i].rev; + if (this.last_req.status == 417) { + return {errors: JSON.parse(this.last_req.responseText)}; + } + else { + CouchDB.maybeThrowError(this.last_req); + var results = JSON.parse(this.last_req.responseText); + for (var i = 0; i < docs.length; i++) { + if(results[i].rev) + docs[i]._rev = results[i].rev; + } + return results; } - return result; } this.ensureFullCommit = function() { @@ -203,6 +214,20 @@ function CouchDB(name, httpHeaders) { return JSON.parse(this.last_req.responseText); } + this.setDbProperty = function(propId, propValue) { + this.last_req = this.request("PUT", this.uri + propId,{ + body:JSON.stringify(propValue) + }); + CouchDB.maybeThrowError(this.last_req); + return JSON.parse(this.last_req.responseText); + } + + this.getDbProperty = function(propId) { + this.last_req = this.request("GET", this.uri + propId); + CouchDB.maybeThrowError(this.last_req); + return JSON.parse(this.last_req.responseText); + } + this.setAdmins = function(adminsArray) { this.last_req = this.request("PUT", this.uri + "_admins",{ body:JSON.stringify(adminsArray) @@ -283,8 +308,11 @@ CouchDB.getVersion = function() { return JSON.parse(CouchDB.last_req.responseText).version; } -CouchDB.replicate = function(source, target) { +CouchDB.replicate = function(source, target, rep_options) { + rep_options = rep_options || {}; + var headers = rep_options.headers || {}; CouchDB.last_req = CouchDB.request("POST", "/_replicate", { + headers: headers, body: JSON.stringify({source: source, target: target}) }); CouchDB.maybeThrowError(CouchDB.last_req); diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index 64c366b6..8ba1f76d 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -68,6 +68,7 @@ loadTest("purge.js"); loadTest("config.js"); loadTest("security_validation.js"); loadTest("stats.js"); +loadTest("rev_stemming.js"); function makeDocs(start, end, templateDoc) { var templateDocSrc = templateDoc ? JSON.stringify(templateDoc) : "{}" diff --git a/share/www/script/test/attachment_names.js b/share/www/script/test/attachment_names.js index 802abc08..3c694dd0 100644 --- a/share/www/script/test/attachment_names.js +++ b/share/www/script/test/attachment_names.js @@ -10,7 +10,7 @@ // License for the specific language governing permissions and limitations under // the License. -couchTests.attatchment_names = function(debug) { +couchTests.attachment_names = function(debug) { var db = new CouchDB("test_suite_db"); db.deleteDb(); db.createDb(); diff --git a/share/www/script/test/attachments.js b/share/www/script/test/attachments.js index 409426c7..4b43e31a 100644 --- a/share/www/script/test/attachments.js +++ b/share/www/script/test/attachments.js @@ -32,8 +32,8 @@ couchTests.attachments= function(debug) { var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"); T(xhr.responseText == "This is a base64 encoded text"); T(xhr.getResponseHeader("Content-Type") == "text/plain"); - T(xhr.getResponseHeader("Etag") == save_response.rev); - + T(xhr.getResponseHeader("Etag") == '"' + save_response.rev + '"'); + // empty attachment var binAttDoc2 = { _id: "bin_doc2", diff --git a/share/www/script/test/basics.js b/share/www/script/test/basics.js index 89e99a64..2837b77a 100644 --- a/share/www/script/test/basics.js +++ b/share/www/script/test/basics.js @@ -14,14 +14,14 @@ couchTests.basics = function(debug) { var result = JSON.parse(CouchDB.request("GET", "/").responseText); T(result.couchdb == "Welcome"); - + var db = new CouchDB("test_suite_db"); db.deleteDb(); // bug COUCHDB-100: DELETE on non-existent DB returns 500 instead of 404 db.deleteDb(); - -db.createDb(); + + db.createDb(); // PUT on existing DB should return 412 instead of 500 xhr = CouchDB.request("PUT", "/test_suite_db/"); @@ -122,14 +122,14 @@ db.createDb(); // make sure we can still open the old rev of the deleted doc T(db.open(existingDoc._id, {rev: existingDoc._rev}) != null); - + // make sure restart works T(db.ensureFullCommit().ok); restartServer(); - + // make sure we can still open T(db.open(existingDoc._id, {rev: existingDoc._rev}) != null); - + // test that the POST response has a Location header var xhr = CouchDB.request("POST", "/test_suite_db", { body: JSON.stringify({"foo":"bar"}) diff --git a/share/www/script/test/bulk_docs.js b/share/www/script/test/bulk_docs.js index d781b2c6..40a36331 100644 --- a/share/www/script/test/bulk_docs.js +++ b/share/www/script/test/bulk_docs.js @@ -19,38 +19,73 @@ couchTests.bulk_docs = function(debug) { var docs = makeDocs(5); // Create the docs - var result = db.bulkSave(docs); - T(result.ok); - T(result.new_revs.length == 5); + var results = db.bulkSave(docs); + + T(results.length == 5); for (var i = 0; i < 5; i++) { - T(result.new_revs[i].id == docs[i]._id); - T(result.new_revs[i].rev); + T(results[i].id == docs[i]._id); + T(results[i].rev); + // Update the doc docs[i].string = docs[i].string + ".00"; } - // Update the docs - result = db.bulkSave(docs); - T(result.ok); - T(result.new_revs.length == 5); + // Save the docs + results = db.bulkSave(docs); + T(results.length == 5); for (i = 0; i < 5; i++) { - T(result.new_revs[i].id == i.toString()); + T(results[i].id == i.toString()); + + // set the delete flag to delete the docs in the next step docs[i]._deleted = true; } + + // now test a bulk update with a conflict + // open and save + var doc = db.open("0"); + db.save(doc); - // Delete the docs - result = db.bulkSave(docs); - T(result.ok); - T(result.new_revs.length == 5); - for (i = 0; i < 5; i++) { + // Now bulk delete the docs + results = db.bulkSave(docs); + + // doc "0" should be a conflict + T(results.length == 5); + T(results[0].id == "0"); + T(results[0].error == "conflict"); + T(results[0].rev === undefined); // no rev member when a conflict + + // but the rest are not + for (i = 1; i < 5; i++) { + T(results[i].id == i.toString()); + T(results[i].rev) T(db.open(docs[i]._id) == null); } + + // now force a conflict to to save + + // save doc 0, this will cause a conflict when we save docs[0] + var doc = db.open("0"); + docs[0] = db.open("0") + db.save(doc); + + docs[0].shooby = "dooby"; + + // Now save the bulk docs, When we use all_or_nothing, we don't get conflict + // checking, all docs are saved regardless of conflict status, or none are + // saved. + results = db.bulkSave(docs,{all_or_nothing:true}); + T(results.error === undefined); + + var doc = db.open("0", {conflicts:true}); + var docConflict = db.open("0", {rev:doc._conflicts[0]}); + + T(doc.shooby == "dooby" || docConflict.shooby == "dooby"); // verify creating a document with no id returns a new id var req = CouchDB.request("POST", "/test_suite_db/_bulk_docs", { body: JSON.stringify({"docs": [{"foo":"bar"}]}) }); - result = JSON.parse(req.responseText); + results = JSON.parse(req.responseText); - T(result.new_revs[0].id != ""); - T(result.new_revs[0].rev != ""); + T(results[0].id != ""); + T(results[0].rev != ""); }; diff --git a/share/www/script/test/compact.js b/share/www/script/test/compact.js index 4612621e..51a57051 100644 --- a/share/www/script/test/compact.js +++ b/share/www/script/test/compact.js @@ -16,8 +16,7 @@ couchTests.compact = function(debug) { db.createDb(); if (debug) debugger; var docs = makeDocs(0, 10); - var saveResult = db.bulkSave(docs); - T(saveResult.ok); + db.bulkSave(docs); var binAttDoc = { _id: "bin_doc", @@ -36,12 +35,11 @@ couchTests.compact = function(debug) { for(var i in docs) { db.deleteDoc(docs[i]); } - db.setAdmins(["Foo bar"]); var deletesize = db.info().disk_size; T(deletesize > originalsize); - var xhr = CouchDB.request("POST", "/test_suite_db/_compact"); - T(xhr.status == 202); + T(db.compact().ok); + T(db.last_req.status == 202); // compaction isn't instantaneous, loop until done while (db.info().compact_running) {}; diff --git a/share/www/script/test/design_docs.js b/share/www/script/test/design_docs.js index aedf741a..ebf0e74d 100644 --- a/share/www/script/test/design_docs.js +++ b/share/www/script/test/design_docs.js @@ -43,7 +43,7 @@ couchTests.design_docs = function(debug) { } T(db.save(designDoc).ok); - T(db.bulkSave(makeDocs(1, numDocs + 1)).ok); + db.bulkSave(makeDocs(1, numDocs + 1)); // test that the _all_docs view returns correctly with keys var results = db.allDocs({startkey:"_design", endkey:"_design0"}); diff --git a/share/www/script/test/etags_views.js b/share/www/script/test/etags_views.js index cb38ccf6..018bdc25 100644 --- a/share/www/script/test/etags_views.js +++ b/share/www/script/test/etags_views.js @@ -42,8 +42,7 @@ couchTests.etags_views = function(debug) { T(db.save(designDoc).ok); var xhr; var docs = makeDocs(0, 10); - var saveResult = db.bulkSave(docs); - T(saveResult.ok); + db.bulkSave(docs); // verify get w/Etag on map view xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView"); diff --git a/share/www/script/test/invalid_docids.js b/share/www/script/test/invalid_docids.js index 6ed01f0b..814d1a21 100644 --- a/share/www/script/test/invalid_docids.js +++ b/share/www/script/test/invalid_docids.js @@ -23,32 +23,32 @@ couchTests.invalid_docids = function(debug) { //Test non-string try { db.save({"_id": 1}); - T(1 == 0); + T(1 == 0, "doc id must be string"); } catch(e) { T(db.last_req.status == 400); - T(e.error == "invalid_doc"); + T(e.error == "bad_request"); } // Test invalid _prefix try { db.save({"_id": "_invalid"}); - T(1 == 0); + T(1 == 0, "doc id may not start with underscore"); } catch(e) { T(db.last_req.status == 400); - T(e.error == "invalid_doc"); + T(e.error == "bad_request"); } // Test _bulk_docs explicitly. var docs = [{"_id": "_design/foo"}, {"_id": "_local/bar"}]; - T(db.bulkSave(docs).ok); + db.bulkSave(docs); docs.forEach(function(d) {T(db.open(d._id)._id == d._id);}); docs = [{"_id": "_invalid"}]; try { db.bulkSave(docs); - T(1 == 0); + T(1 == 0, "doc id may not start with underscore, even in bulk docs"); } catch(e) { T(db.last_req.status == 400); - T(e.error == "invalid_doc"); + T(e.error == "bad_request"); } }; diff --git a/share/www/script/test/list_views.js b/share/www/script/test/list_views.js index 055ef51c..e71ebc4e 100644 --- a/share/www/script/test/list_views.js +++ b/share/www/script/test/list_views.js @@ -137,8 +137,7 @@ couchTests.list_views = function(debug) { T(db.save(designDoc).ok); var docs = makeDocs(0, 10); - var saveResult = db.bulkSave(docs); - T(saveResult.ok); + db.bulkSave(docs); var view = db.view('lists/basicView'); T(view.total_rows == 10); @@ -207,8 +206,7 @@ couchTests.list_views = function(debug) { // verify the etags expire correctly var docs = makeDocs(11, 12); - var saveResult = db.bulkSave(docs); - T(saveResult.ok); + db.bulkSave(docs); xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?group=true", { headers: {"if-none-match": etag} diff --git a/share/www/script/test/lots_of_docs.js b/share/www/script/test/lots_of_docs.js index 702317e5..4101e6a7 100644 --- a/share/www/script/test/lots_of_docs.js +++ b/share/www/script/test/lots_of_docs.js @@ -25,7 +25,7 @@ couchTests.lots_of_docs = function(debug) { for(var i=0; i < numDocsToCreate; i += 100) { var createNow = Math.min(numDocsToCreate - i, 100); var docs = makeDocs(i, i + createNow); - T(db.bulkSave(docs).ok); + db.bulkSave(docs); } // query all documents, and return the doc.integer member as a key. diff --git a/share/www/script/test/purge.js b/share/www/script/test/purge.js index e05ac024..0b47c0d0 100644 --- a/share/www/script/test/purge.js +++ b/share/www/script/test/purge.js @@ -34,7 +34,7 @@ couchTests.purge = function(debug) { T(db.save(designDoc).ok); - T(db.bulkSave(makeDocs(1, numDocs + 1)).ok); + db.bulkSave(makeDocs(1, numDocs + 1)); // go ahead and validate the views before purging var rows = db.view("test/all_docs_twice").rows; diff --git a/share/www/script/test/reduce.js b/share/www/script/test/reduce.js index d3245f98..c8bdcd92 100644 --- a/share/www/script/test/reduce.js +++ b/share/www/script/test/reduce.js @@ -17,7 +17,7 @@ couchTests.reduce = function(debug) { if (debug) debugger; var numDocs = 500 var docs = makeDocs(1,numDocs + 1); - T(db.bulkSave(docs).ok); + db.bulkSave(docs); var summate = function(N) {return (N+1)*N/2;}; var map = function (doc) { @@ -65,7 +65,7 @@ couchTests.reduce = function(debug) { docs.push({keys:["d", "a"]}); docs.push({keys:["d", "b"]}); docs.push({keys:["d", "c"]}); - T(db.bulkSave(docs).ok); + db.bulkSave(docs); T(db.info().doc_count == ((i - 1) * 10 * 11) + ((j + 1) * 11)); } @@ -157,7 +157,7 @@ couchTests.reduce = function(debug) { docs.push({val:80}); docs.push({val:90}); docs.push({val:100}); - T(db.bulkSave(docs).ok); + db.bulkSave(docs); } var results = db.query(map, reduceCombine); diff --git a/share/www/script/test/reduce_false.js b/share/www/script/test/reduce_false.js index 41f0c669..22ef2e8b 100644 --- a/share/www/script/test/reduce_false.js +++ b/share/www/script/test/reduce_false.js @@ -18,7 +18,7 @@ couchTests.reduce_false = function(debug) { var numDocs = 5; var docs = makeDocs(1,numDocs + 1); - T(db.bulkSave(docs).ok); + db.bulkSave(docs); var summate = function(N) {return (N+1)*N/2;}; var designDoc = { diff --git a/share/www/script/test/replication.js b/share/www/script/test/replication.js index 59dc4700..944c8e47 100644 --- a/share/www/script/test/replication.js +++ b/share/www/script/test/replication.js @@ -59,7 +59,7 @@ couchTests.replication = function(debug) { simple_test: new function () { this.init = function(dbA, dbB) { var docs = makeDocs(0, numDocs); - T(dbA.bulkSave(docs).ok); + dbA.bulkSave(docs); }; this.afterAB1 = function(dbA, dbB) { @@ -160,7 +160,7 @@ couchTests.replication = function(debug) { dbA.deleteDoc({_id:"foo", _rev:docA._conflicts[0]}); }; - this.afterBA2 = function(dbA, dbB) { + this.afterBA2 = function(dbA, dbB) { // open documents and include the conflict meta data var docA = dbA.open("foo", {conflicts: true}); var docB = dbB.open("foo", {conflicts: true}); @@ -179,29 +179,56 @@ couchTests.replication = function(debug) { } } - T(CouchDB.replicate(A, B).ok); + var result = CouchDB.replicate(A, B); + + var seqA = result.source_last_seq; + T(0 == result.history[0].start_last_seq); + T(result.history[1] === undefined) for(test in repTests) { if(repTests[test].afterAB1) repTests[test].afterAB1(dbA, dbB); } - T(CouchDB.replicate(B, A).ok); + result = CouchDB.replicate(B, A); + + var seqB = result.source_last_seq; + T(0 == result.history[0].start_last_seq); + T(result.history[1] === undefined) for(test in repTests) { if(repTests[test].afterBA1) repTests[test].afterBA1(dbA, dbB); } - T(CouchDB.replicate(A, B).ok); + var result2 = CouchDB.replicate(A, B); + + // each successful replication produces a new session id + T(result2.session_id != result.session_id); + + T(seqA < result2.source_last_seq); + T(seqA == result2.history[0].start_last_seq); + T(result2.history[1].end_last_seq == seqA) + + seqA = result2.source_last_seq; for(test in repTests) { if(repTests[test].afterAB2) repTests[test].afterAB2(dbA, dbB); } - T(CouchDB.replicate(B, A).ok); + result = CouchDB.replicate(B, A) + + T(seqB < result.source_last_seq); + T(seqB == result.history[0].start_last_seq); + T(result.history[1].end_last_seq == seqB) + + seqB = result.source_last_seq; for(test in repTests) { if(repTests[test].afterBA2) repTests[test].afterBA2(dbA, dbB); } - + + // do an replication where nothing has changed + result2 = CouchDB.replicate(B, A); + T(result2.no_changes == true); + T(result2.session_id == result.session_id); } }; diff --git a/share/www/script/test/rev_stemming.js b/share/www/script/test/rev_stemming.js new file mode 100644 index 00000000..f629c299 --- /dev/null +++ b/share/www/script/test/rev_stemming.js @@ -0,0 +1,93 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.rev_stemming = function(debug) { + var db = new CouchDB("test_suite_db_a"); + db.deleteDb(); + db.createDb(); + var dbB = new CouchDB("test_suite_db_b"); + dbB.deleteDb(); + dbB.createDb(); + if (debug) debugger; + + var newLimit = 5; + + T(db.getDbProperty("_revs_limit") == 1000); + + var doc = {_id:"foo",foo:0} + for( var i=0; i < newLimit + 1; i++) { + doc.foo++; + T(db.save(doc).ok); + } + var doc0 = db.open("foo", {revs:true}); + T(doc0._revisions.ids.length == newLimit + 1); + + var docBar = {_id:"bar",foo:0} + for( var i=0; i < newLimit + 1; i++) { + docBar.foo++; + T(db.save(docBar).ok); + } + T(db.open("bar", {revs:true})._revisions.ids.length == newLimit + 1); + + T(db.setDbProperty("_revs_limit", newLimit).ok); + + for( var i=0; i < newLimit + 1; i++) { + doc.foo++; + T(db.save(doc).ok); + } + doc0 = db.open("foo", {revs:true}); + T(doc0._revisions.ids.length == newLimit); + + + // If you replicate after you make more edits than the limit, you'll + // cause a spurious edit conflict. + CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); + var docB1 = dbB.open("foo",{conflicts:true}) + T(docB1._conflicts == null); + + for( var i=0; i < newLimit - 1; i++) { + doc.foo++; + T(db.save(doc).ok); + } + + // one less edit than limit, no conflict + CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); + var docB1 = dbB.open("foo",{conflicts:true}) + T(docB1._conflicts == null); + + //now we hit the limit + for( var i=0; i < newLimit; i++) { + doc.foo++; + T(db.save(doc).ok); + } + + CouchDB.replicate("test_suite_db_a", "test_suite_db_b"); + + var docB2 = dbB.open("foo",{conflicts:true}); + + // we have a conflict, but the previous replicated rev is always the losing + // conflict + T(docB2._conflicts[0] == docB1._rev) + + // We having already updated bar before setting the limit, so it's still got + // a long rev history. compact to stem the revs. + + T(db.open("bar", {revs:true})._revisions.ids.length == newLimit + 1); + + T(db.compact().ok); + + // compaction isn't instantaneous, loop until done + while (db.info().compact_running) {}; + + T(db.open("bar", {revs:true})._revisions.ids.length == newLimit); + +}; diff --git a/share/www/script/test/security_validation.js b/share/www/script/test/security_validation.js index 61f56b39..a41d8d70 100644 --- a/share/www/script/test/security_validation.js +++ b/share/www/script/test/security_validation.js @@ -31,28 +31,27 @@ couchTests.security_validation = function(debug) { // the "username:password" string, it's sent completely plain text. // Firefox and Safari both deal with this correctly (which is to say // they correctly do nothing special). - - + + var db = new CouchDB("test_suite_db"); db.deleteDb(); db.createDb(); if (debug) debugger; - + run_on_modified_server( [{section: "httpd", key: "authentication_handler", value: "{couch_httpd, special_test_authentication_handler}"}, - {section:"httpd", + {section:"httpd", key: "WWW-Authenticate", value: "X-Couch-Test-Auth"}], - - function () { + function () { // try saving document usin the wrong credentials var wrongPasswordDb = new CouchDB("test_suite_db", {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:foo"} ); - + try { wrongPasswordDb.save({foo:1,author:"Damien Katz"}); T(false && "Can't get here. Should have thrown an error 1"); @@ -60,8 +59,8 @@ couchTests.security_validation = function(debug) { T(e.error == "unauthorized"); T(wrongPasswordDb.last_req.status == 401); } - - + + // Create the design doc that will run custom validation code var designDoc = { _id:"_design/test", @@ -83,9 +82,9 @@ couchTests.security_validation = function(debug) { var userDb = new CouchDB("test_suite_db", {"WWW-Authenticate": "X-Couch-Test-Auth Damien Katz:pecan pie"} ); - + T(userDb.save({_id:"testdoc", foo:1, author:"Damien Katz"}).ok); - + // Attempt to save the design as a non-admin try { userDb.save(designDoc); @@ -94,17 +93,17 @@ couchTests.security_validation = function(debug) { T(e.error == "unauthorized"); T(userDb.last_req.status == 401); } - - // add user as admin - db.setAdmins(["Damien Katz"]); - + + // set user as the admin + T(db.setDbProperty("_admins", ["Damien Katz"]).ok); + T(userDb.save(designDoc).ok); - + // update the document var doc = userDb.open("testdoc"); doc.foo=2; T(userDb.save(doc).ok); - + // Save a document that's missing an author field. try { userDb.save({foo:1}); @@ -113,12 +112,12 @@ couchTests.security_validation = function(debug) { T(e.error == "forbidden"); T(userDb.last_req.status == 403); } - + // Now attempt to update the document as a different user, Jan var user2Db = new CouchDB("test_suite_db", {"WWW-Authenticate": "X-Couch-Test-Auth Jan Lehnardt:apple"} ); - + var doc = user2Db.open("testdoc"); doc.foo=3; try { @@ -128,17 +127,17 @@ couchTests.security_validation = function(debug) { T(e.error == "unauthorized"); T(user2Db.last_req.status == 401); } - + // Now have Damien change the author to Jan doc = userDb.open("testdoc"); doc.author="Jan Lehnardt"; T(userDb.save(doc).ok); - + // Now update the document as Jan doc = user2Db.open("testdoc"); doc.foo = 3; T(user2Db.save(doc).ok); - + // Damien can't delete it try { userDb.deleteDoc(doc); @@ -147,8 +146,126 @@ couchTests.security_validation = function(debug) { T(e.error == "unauthorized"); T(userDb.last_req.status == 401); } - + // Now delete document - T(user2Db.deleteDoc(doc).ok); + T(user2Db.deleteDoc(doc).ok); + + // now test bulk docs + var docs = [{_id:"bahbah",author:"Damien Katz",foo:"bar"},{_id:"fahfah",foo:"baz"}]; + + // Create the docs + var results = db.bulkSave(docs); + + T(results[0].rev) + T(results[0].error == undefined) + T(results[1].rev === undefined) + T(results[1].error == "forbidden") + + T(db.open("bahbah")); + T(db.open("fahfah") == null); + + + // now all or nothing with a failure + var docs = [{_id:"booboo",author:"Damien Katz",foo:"bar"},{_id:"foofoo",foo:"baz"}]; + + // Create the docs + var results = db.bulkSave(docs, {all_or_nothing:true}); + + T(results.errors.length == 1); + T(results.errors[0].error == "forbidden"); + T(db.open("booboo") == null); + T(db.open("foofoo") == null); + + + // Now test replication + var AuthHeaders = {"WWW-Authenticate": "X-Couch-Test-Auth Christopher Lenz:dog food"}; + var host = CouchDB.host; + var dbPairs = [ + {source:"test_suite_db_a", + target:"test_suite_db_b"}, + + {source:"test_suite_db_a", + target:{url: "http://" + host + "/test_suite_db_b", + headers: AuthHeaders}}, + + {source:{url:"http://" + host + "/test_suite_db_a", + headers: AuthHeaders}, + target:"test_suite_db_b"}, + + {source:{url:"http://" + host + "/test_suite_db_a", + headers: AuthHeaders}, + target:{url:"http://" + host + "/test_suite_db_b", + headers: AuthHeaders}}, + ] + var adminDbA = new CouchDB("test_suite_db_a"); + var adminDbB = new CouchDB("test_suite_db_b"); + var dbA = new CouchDB("test_suite_db_a", + {"WWW-Authenticate": "X-Couch-Test-Auth Christopher Lenz:dog food"}); + var dbB = new CouchDB("test_suite_db_b", + {"WWW-Authenticate": "X-Couch-Test-Auth Christopher Lenz:dog food"}); + var xhr; + for (var testPair = 0; testPair < dbPairs.length; testPair++) { + var A = dbPairs[testPair].source + var B = dbPairs[testPair].target + + adminDbA.deleteDb(); + adminDbA.createDb(); + adminDbB.deleteDb(); + adminDbB.createDb(); + + // save and replicate a documents that will and will not pass our design + // doc validation function. + dbA.save({_id:"foo1",value:"a",author:"Noah Slater"}); + dbA.save({_id:"foo2",value:"a",author:"Christopher Lenz"}); + dbA.save({_id:"bad1",value:"a"}); + + T(CouchDB.replicate(A, B, {headers:AuthHeaders}).ok); + T(CouchDB.replicate(B, A, {headers:AuthHeaders}).ok); + + T(dbA.open("foo1")); + T(dbB.open("foo1")); + T(dbA.open("foo2")); + T(dbB.open("foo2")); + + // save the design doc to dbA + delete designDoc._rev; // clear rev from previous saves + adminDbA.save(designDoc); + + // no affect on already saved docs + T(dbA.open("bad1")); + + // Update some docs on dbB. Since the design hasn't replicated, anything + // is allowed. + + // this edit will fail validation on replication to dbA (no author) + T(dbB.save({_id:"bad2",value:"a"}).ok); + + // this edit will fail security on replication to dbA (wrong author + // replicating the change) + var foo1 = dbB.open("foo1"); + foo1.value = "b"; + dbB.save(foo1); + + // this is a legal edit + var foo2 = dbB.open("foo2"); + foo2.value = "b"; + dbB.save(foo2); + + var results = CouchDB.replicate(B, A, {headers:AuthHeaders}); + + T(results.ok); + + T(results.history[0].docs_written == 1); + T(results.history[0].doc_write_failures == 2); + + // bad2 should not be on dbA + T(dbA.open("bad2") == null); + + // The edit to foo1 should not have replicated. + T(dbA.open("foo1").value == "a"); + + // The edit to foo2 should have replicated. + T(dbA.open("foo2").value == "b"); + } }); }; diff --git a/share/www/script/test/view_include_docs.js b/share/www/script/test/view_include_docs.js index dd8ebb44..2c47b696 100644 --- a/share/www/script/test/view_include_docs.js +++ b/share/www/script/test/view_include_docs.js @@ -17,7 +17,7 @@ couchTests.view_include_docs = function(debug) { if (debug) debugger; var docs = makeDocs(0, 100); - T(db.bulkSave(docs).ok); + db.bulkSave(docs); var designDoc = { _id:"_design/test", diff --git a/share/www/script/test/view_multi_key_all_docs.js b/share/www/script/test/view_multi_key_all_docs.js index f036db11..8e2464b4 100644 --- a/share/www/script/test/view_multi_key_all_docs.js +++ b/share/www/script/test/view_multi_key_all_docs.js @@ -17,7 +17,7 @@ couchTests.view_multi_key_all_docs = function(debug) { if (debug) debugger; var docs = makeDocs(0, 100); - T(db.bulkSave(docs).ok); + db.bulkSave(docs); var keys = ["10","15","30","37","50"]; var rows = db.allDocs({},keys).rows; diff --git a/share/www/script/test/view_multi_key_design.js b/share/www/script/test/view_multi_key_design.js index 2464ff1c..c2833910 100644 --- a/share/www/script/test/view_multi_key_design.js +++ b/share/www/script/test/view_multi_key_design.js @@ -17,7 +17,7 @@ couchTests.view_multi_key_design = function(debug) { if (debug) debugger; var docs = makeDocs(0, 100); - T(db.bulkSave(docs).ok); + db.bulkSave(docs); var designDoc = { _id:"_design/test", diff --git a/share/www/script/test/view_multi_key_temp.js b/share/www/script/test/view_multi_key_temp.js index 28cd42b4..545e2520 100644 --- a/share/www/script/test/view_multi_key_temp.js +++ b/share/www/script/test/view_multi_key_temp.js @@ -17,7 +17,7 @@ couchTests.view_multi_key_temp = function(debug) { if (debug) debugger; var docs = makeDocs(0, 100); - T(db.bulkSave(docs).ok); + db.bulkSave(docs); var queryFun = function(doc) { emit(doc.integer, doc.integer) }; var reduceFun = function (keys, values) { return sum(values); }; diff --git a/share/www/script/test/view_pagination.js b/share/www/script/test/view_pagination.js index 7d466f19..21eab888 100644 --- a/share/www/script/test/view_pagination.js +++ b/share/www/script/test/view_pagination.js @@ -17,7 +17,7 @@ couchTests.view_pagination = function(debug) { if (debug) debugger; var docs = makeDocs(0, 100); - T(db.bulkSave(docs).ok); + db.bulkSave(docs); var queryFun = function(doc) { emit(doc.integer, null) }; var i; -- cgit v1.2.3