Repository: couchdb Updated Branches: refs/heads/goodbye-futon 4bf97fc99 -> 263182c8c (forced update)
http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/users_db_security.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/users_db_security.js b/test/javascript/tests/users_db_security.js new file mode 100644 index 0000000..f2ca8bc --- /dev/null +++ b/test/javascript/tests/users_db_security.js @@ -0,0 +1,423 @@ +// 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.users_db_security = function(debug) { + var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); + if (debug) debugger; + + function wait(ms) { + var t0 = new Date(), t1; + do { + CouchDB.request("GET", "/"); + t1 = new Date(); + } while ((t1 - t0) <= ms); + } + + var loginUser = function(username) { + var pws = { + jan: "apple", + jchris: "mp3", + jchris1: "couch", + fdmanana: "foobar", + benoitc: "test" + }; + var username1 = username.replace(/[0-9]$/, ""); + var password = pws[username]; + T(CouchDB.login(username1, pws[username]).ok); + }; + + var open_as = function(db, docId, username) { + loginUser(username); + try { + return db.open(docId, {"anti-cache": Math.round(Math.random() * 100000)}); + } finally { + CouchDB.logout(); + } + }; + + var view_as = function(db, viewname, username) { + loginUser(username); + try { + return db.view(viewname); + } finally { + CouchDB.logout(); + } + }; + + var save_as = function(db, doc, username) + { + loginUser(username); + try { + return db.save(doc); + } catch (ex) { + return ex; + } finally { + CouchDB.logout(); + } + }; + + var changes_as = function(db, username) + { + loginUser(username); + try { + return db.changes(); + } catch(ex) { + return ex; + } finally { + CouchDB.logout(); + } + }; + + var testFun = function() + { + + // _users db + // a doc with a field 'password' should be hashed to 'derived_key' + // with salt and salt stored in 'salt', 'password' is set to null. + // Exising 'derived_key' and 'salt' fields are overwritten with new values + // when a non-null 'password' field exists. + // anonymous should be able to create a user document + var userDoc = { + _id: "org.couchdb.user:jchris", + type: "user", + name: "jchris", + password: "mp3", + roles: [] + }; + + // jan's gonna be admin as he's the first user + TEquals(true, usersDb.save(userDoc).ok, "should save document"); + userDoc = usersDb.open("org.couchdb.user:jchris"); + TEquals(undefined, userDoc.password, "password field should be null 1"); + TEquals(40, userDoc.derived_key.length, "derived_key should exist"); + TEquals(32, userDoc.salt.length, "salt should exist"); + + // create server admin + run_on_modified_server([ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "admins", + key: "jan", + value: "apple" + } + ], function() { + + // anonymous should not be able to read an existing user's user document + var res = usersDb.open("org.couchdb.user:jchris"); + TEquals(null, res, "anonymous user doc read should be not found"); + + // anonymous should not be able to read /_users/_changes + try { + var ch = usersDb.changes(); + T(false, "anonymous can read _changes"); + } catch(e) { + TEquals("unauthorized", e.error, "anoymous can't read _changes"); + } + + // user should be able to read their own document + var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris"); + TEquals("org.couchdb.user:jchris", jchrisDoc._id); + + // user should not be able to read /_users/_changes + var changes = changes_as(usersDb, "jchris"); + TEquals("unauthorized", changes.error, "user can't read _changes"); + + // new 'password' fields should trigger new hashing routine + jchrisDoc.password = "couch"; + + TEquals(true, save_as(usersDb, jchrisDoc, "jchris").ok); + wait(100); + var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris1"); + + TEquals(undefined, jchrisDoc.password, "password field should be null 2"); + TEquals(40, jchrisDoc.derived_key.length, "derived_key should exist"); + TEquals(32, jchrisDoc.salt.length, "salt should exist"); + + TEquals(true, userDoc.salt != jchrisDoc.salt, "should have new salt"); + TEquals(true, userDoc.derived_key != jchrisDoc.derived_key, + "should have new derived_key"); + + // SHA-1 password hashes are upgraded to PBKDF2 on successful + // authentication + var rnewsonDoc = { + _id: "org.couchdb.user:rnewson", + type: "user", + name: "rnewson", + // password: "plaintext_password", + password_sha: "e29dc3aeed5abf43185c33e479f8998558c59474", + salt: "24f1e0a87c2e374212bda1073107e8ae", + roles: [] + }; + + var password_sha = rnewsonDoc.password_sha, + salt = rnewsonDoc.salt, + derived_key, + iterations; + + usersDb.save(rnewsonDoc); + rnewsonDoc = open_as(usersDb, rnewsonDoc._id, "jan"); + T(!rnewsonDoc.password_scheme); + T(!rnewsonDoc.derived_key); + T(!rnewsonDoc.iterations); + + // check that we don't upgrade when the password is wrong + TEquals("unauthorized", CouchDB.login("rnewson", "wrong_password").error); + rnewsonDoc = open_as(usersDb, rnewsonDoc._id, "jan"); + TEquals(salt, rnewsonDoc.salt); + TEquals(password_sha, rnewsonDoc.password_sha); + T(!rnewsonDoc.password_scheme); + T(!rnewsonDoc.derived_key); + T(!rnewsonDoc.iterations); + + TEquals(true, CouchDB.login("rnewson", "plaintext_password").ok); + rnewsonDoc = usersDb.open(rnewsonDoc._id); + TEquals("pbkdf2", rnewsonDoc.password_scheme); + T(rnewsonDoc.salt != salt); + T(!rnewsonDoc.password_sha); + T(rnewsonDoc.derived_key); + T(rnewsonDoc.iterations); + + salt = rnewsonDoc.salt, + derived_key = rnewsonDoc.derived_key, + iterations = rnewsonDoc.iterations; + + // check that authentication is still working + // and everything is staying the same now + CouchDB.logout(); + TEquals(true, CouchDB.login("rnewson", "plaintext_password").ok); + rnewsonDoc = usersDb.open(rnewsonDoc._id); + TEquals("pbkdf2", rnewsonDoc.password_scheme); + TEquals(salt, rnewsonDoc.salt); + T(!rnewsonDoc.password_sha); + TEquals(derived_key, rnewsonDoc.derived_key); + TEquals(iterations, rnewsonDoc.iterations); + + CouchDB.logout(); + + // user should not be able to read another user's user document + var fdmananaDoc = { + _id: "org.couchdb.user:fdmanana", + type: "user", + name: "fdmanana", + password: "foobar", + roles: [] + }; + + usersDb.save(fdmananaDoc); + + var fdmananaDocAsReadByjchris = + open_as(usersDb, "org.couchdb.user:fdmanana", "jchris1"); + TEquals(null, fdmananaDocAsReadByjchris, + "should not_found opening another user's user doc"); + + + // save a db admin + var benoitcDoc = { + _id: "org.couchdb.user:benoitc", + type: "user", + name: "benoitc", + password: "test", + roles: ["user_admin"] + }; + save_as(usersDb, benoitcDoc, "jan"); + + TEquals(true, CouchDB.login("jan", "apple").ok); + T(usersDb.setSecObj({ + "admins" : { + roles : [], + names : ["benoitc"] + } + }).ok); + CouchDB.logout(); + + // user should not be able to read from any view + var ddoc = { + _id: "_design/user_db_auth", + views: { + test: { + map: "function(doc) { emit(doc._id, null); }" + } + } + }; + + save_as(usersDb, ddoc, "jan"); + + try { + usersDb.view("user_db_auth/test"); + T(false, "user had access to view in admin db"); + } catch(e) { + TEquals("forbidden", e.error, + "non-admins should not be able to read a view"); + } + + // admin should be able to read from any view + var result = view_as(usersDb, "user_db_auth/test", "jan"); + TEquals(4, result.total_rows, "should allow access and list four users to admin"); + + // db admin should be able to read from any view + var result = view_as(usersDb, "user_db_auth/test", "benoitc"); + TEquals(4, result.total_rows, "should allow access and list four users to db admin"); + + + // non-admins can't read design docs + try { + open_as(usersDb, "_design/user_db_auth", "jchris1"); + T(false, "non-admin read design doc, should not happen"); + } catch(e) { + TEquals("forbidden", e.error, "non-admins can't read design docs"); + } + + // admin should be able to read and edit any user doc + fdmananaDoc.password = "mobile"; + var result = save_as(usersDb, fdmananaDoc, "jan"); + TEquals(true, result.ok, "admin should be able to update any user doc"); + + // admin should be able to read and edit any user doc + fdmananaDoc.password = "mobile1"; + var result = save_as(usersDb, fdmananaDoc, "benoitc"); + TEquals(true, result.ok, "db admin by role should be able to update any user doc"); + + TEquals(true, CouchDB.login("jan", "apple").ok); + T(usersDb.setSecObj({ + "admins" : { + roles : ["user_admin"], + names : [] + } + }).ok); + CouchDB.logout(); + + // db admin should be able to read and edit any user doc + fdmananaDoc.password = "mobile2"; + var result = save_as(usersDb, fdmananaDoc, "benoitc"); + TEquals(true, result.ok, "db admin should be able to update any user doc"); + + // ensure creation of old-style docs still works + var robertDoc = CouchDB.prepareUserDoc({ name: "robert" }, "anchovy"); + var result = usersDb.save(robertDoc); + TEquals(true, result.ok, "old-style user docs should still be accepted"); + + // log in one last time so run_on_modified_server can clean up the admin account + TEquals(true, CouchDB.login("jan", "apple").ok); + }); + + run_on_modified_server([ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "couch_httpd_auth", + key: "public_fields", + value: "name,type" + }, + { + section: "couch_httpd_auth", + key: "users_db_public", + value: "true" + }, + { + section: "admins", + key: "jan", + value: "apple" + } + ], function() { + var res = usersDb.open("org.couchdb.user:jchris"); + TEquals("jchris", res.name); + TEquals("user", res.type); + TEquals(undefined, res.roles); + TEquals(undefined, res.salt); + TEquals(undefined, res.password_scheme); + TEquals(undefined, res.derived_key); + + TEquals(true, CouchDB.login("jchris", "couch").ok); + + var all = usersDb.allDocs({ include_docs: true }); + T(all.rows); + if (all.rows) { + T(all.rows.every(function(row) { + if (row.doc) { + return Object.keys(row.doc).every(function(key) { + return key === 'name' || key === 'type'; + }); + } else { + if(row.id[0] == "_") { + // ignore design docs + return true + } else { + return false; + } + } + })); + } + // log in one last time so run_on_modified_server can clean up the admin account + TEquals(true, CouchDB.login("jan", "apple").ok); + }); + + run_on_modified_server([ + { + section: "couch_httpd_auth", + key: "iterations", + value: "1" + }, + { + section: "couch_httpd_auth", + key: "public_fields", + value: "name" + }, + { + section: "couch_httpd_auth", + key: "users_db_public", + value: "false" + }, + { + section: "admins", + key: "jan", + value: "apple" + } + ], function() { + TEquals(true, CouchDB.login("jchris", "couch").ok); + + try { + var all = usersDb.allDocs({ include_docs: true }); + T(false); // should never hit + } catch(e) { + TEquals("forbidden", e.error, "should throw"); + } + + // COUCHDB-1888 make sure admins always get all fields + TEquals(true, CouchDB.login("jan", "apple").ok); + var all_admin = usersDb.allDocs({ include_docs: "true" }); + TEquals("user", all_admin.rows[2].doc.type, + "should return type"); + + + // log in one last time so run_on_modified_server can clean up the admin account + TEquals(true, CouchDB.login("jan", "apple").ok); + }); + }; + + usersDb.deleteDb(); + run_on_modified_server( + [{section: "couch_httpd_auth", + key: "iterations", value: "1"}, + {section: "couch_httpd_auth", + key: "authentication_db", value: usersDb.name}], + testFun + ); + usersDb.deleteDb(); // cleanup + +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/utf8.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/utf8.js b/test/javascript/tests/utf8.js new file mode 100644 index 0000000..04f6313 --- /dev/null +++ b/test/javascript/tests/utf8.js @@ -0,0 +1,42 @@ +// 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.utf8 = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var texts = []; + + texts[0] = "1. Ascii: hello" + texts[1] = "2. Russian: Ðа беÑÐµÐ³Ñ Ð¿ÑÑÑÑннÑÑ Ð²Ð¾Ð»Ð½" + texts[2] = "3. Math: â® Eâ da = Q, n â â, â f(i) = â g(i)," + texts[3] = "4. Geek: STARGÎÌTE SG-1" + texts[4] = "5. Braille: â¡â â §â â ¼â â â¡â â â ⠹⠰â â¡£â â " + texts[5] = "6. null \u0000 byte" + + // check that we can save a reload with full fidelity + for (var i=0; i<texts.length; i++) { + T(db.save({_id:i.toString(), text:texts[i]}).ok); + } + + for (var i=0; i<texts.length; i++) { + T(db.open(i.toString()).text == texts[i]); + } + + // check that views and key collation don't blow up + var rows = db.query(function(doc) { emit(null, doc.text) }).rows; + for (var i=0; i<texts.length; i++) { + T(rows[i].value == texts[i]); + } +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/uuids.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/uuids.js b/test/javascript/tests/uuids.js new file mode 100644 index 0000000..d304c4e --- /dev/null +++ b/test/javascript/tests/uuids.js @@ -0,0 +1,149 @@ +// 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.uuids = function(debug) { + var etags = []; + var testHashBustingHeaders = function(xhr) { + T(xhr.getResponseHeader("Cache-Control").match(/no-cache/)); + T(xhr.getResponseHeader("Pragma") == "no-cache"); + + var newetag = xhr.getResponseHeader("ETag"); + T(etags.indexOf(newetag) < 0); + etags[etags.length] = newetag; + + // Removing the time based tests as they break easily when + // running CouchDB on a remote server in regards to the browser + // running the Futon test suite. + // + //var currentTime = new Date(); + //var expiresHeader = Date.parse(xhr.getResponseHeader("Expires")); + //var dateHeader = Date.parse(xhr.getResponseHeader("Date")); + + //T(expiresHeader < currentTime); + //T(currentTime - dateHeader < 3000); + }; + + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // a single UUID without an explicit count + var xhr = CouchDB.request("GET", "/_uuids"); + T(xhr.status == 200); + var result = JSON.parse(xhr.responseText); + T(result.uuids.length == 1); + var first = result.uuids[0]; + testHashBustingHeaders(xhr); + + // a single UUID with an explicit count + xhr = CouchDB.request("GET", "/_uuids?count=1"); + T(xhr.status == 200); + result = JSON.parse(xhr.responseText); + T(result.uuids.length == 1); + var second = result.uuids[0]; + T(first != second); + + // no collisions with 1,000 UUIDs + xhr = CouchDB.request("GET", "/_uuids?count=1000"); + T(xhr.status == 200); + result = JSON.parse(xhr.responseText); + T( result.uuids.length == 1000 ); + var seen = {}; + for(var i in result.uuids) { + var id = result.uuids[i]; + T(seen[id] === undefined); + seen[id] = 1; + } + + // ensure we return a 405 on POST + xhr = CouchDB.request("POST", "/_uuids?count=1000"); + T(xhr.status == 405); + + // Test sequential uuids + var seq_testfun = function() { + xhr = CouchDB.request("GET", "/_uuids?count=1000"); + T(xhr.status == 200); + result = JSON.parse(xhr.responseText); + for(var i = 1; i < result.uuids.length; i++) { + T(result.uuids[i].length == 32); + T(result.uuids[i-1] < result.uuids[i], "Sequential uuids are ordered."); + } + }; + + // test max_uuid_count + var xhr = CouchDB.request("GET", "/_uuids?count=1001"); + TEquals(403, xhr.status, "should error when count > max_count"); + + run_on_modified_server([{ + "section": "uuids", + "key": "algorithm", + "value": "sequential", + }], + seq_testfun + ); + + // Test utc_random uuids + var utc_testfun = function() { + xhr = CouchDB.request("GET", "/_uuids?count=1000"); + T(xhr.status == 200); + result = JSON.parse(xhr.responseText); + T(result.uuids[1].length == 32); + + // no collisions + var seen = {}; + for(var i in result.uuids) { + var id = result.uuids[i]; + T(seen[id] === undefined); + seen[id] = 1; + } + + // roughly ordered + var u1 = result.uuids[1].substr(0, 13); + var u2 = result.uuids[result.uuids.length-1].substr(0, 13); + T(u1 < u2, "UTC uuids are only roughly ordered, so this assertion may fail occasionally. Don't sweat it."); + }; + + run_on_modified_server([{ + "section": "uuids", + "key": "algorithm", + "value": "utc_random" + }], + utc_testfun + ); + + // Test utc_id uuids + var utc_id_suffix = "frog"; + var suffix_testfun = function() { + xhr = CouchDB.request("GET", "/_uuids?count=10"); + T(xhr.status == 200); + result = JSON.parse(xhr.responseText); + for(var i = 1; i < result.uuids.length; i++) { + T(result.uuids[i].length == 14 + utc_id_suffix.length); + T(result.uuids[i].substring(14) == utc_id_suffix); + T(result.uuids[i-1] < result.uuids[i], "utc_id_suffix uuids are ordered."); + } + }; + + run_on_modified_server([{ + "section": "uuids", + "key": "algorithm", + "value": "utc_id" + }, { + "section": "uuids", + "key": "utc_id_suffix", + "value": utc_id_suffix + }], + suffix_testfun + ); + + }; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_collation.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_collation.js b/test/javascript/tests/view_collation.js new file mode 100644 index 0000000..b01a5c5 --- /dev/null +++ b/test/javascript/tests/view_collation.js @@ -0,0 +1,116 @@ +// 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.view_collation = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // NOTE, the values are already in their correct sort order. Consider this + // a specification of collation of json types. + + var values = []; + + // special values sort before all other types + values.push(null); + values.push(false); + values.push(true); + + // then numbers + values.push(1); + values.push(2); + values.push(3.0); + values.push(4); + + // then text, case sensitive + values.push("a"); + values.push("A"); + values.push("aa"); + values.push("b"); + values.push("B"); + values.push("ba"); + values.push("bb"); + + // then arrays. compared element by element until different. + // Longer arrays sort after their prefixes + values.push(["a"]); + values.push(["b"]); + values.push(["b","c"]); + values.push(["b","c", "a"]); + values.push(["b","d"]); + values.push(["b","d", "e"]); + + // then object, compares each key value in the list until different. + // larger objects sort after their subset objects. + values.push({a:1}); + values.push({a:2}); + values.push({b:1}); + values.push({b:2}); + values.push({b:2, a:1}); // Member order does matter for collation. + // CouchDB preserves member order + // but doesn't require that clients will. + // (this test might fail if used with a js engine + // that doesn't preserve order) + values.push({b:2, c:2}); + + for (var i=0; i<values.length; i++) { + db.save({_id:(i).toString(), foo:values[i]}); + } + + var queryFun = function(doc) { emit(doc.foo, null); }; + var rows = db.query(queryFun).rows; + for (i=0; i<values.length; i++) { + T(equals(rows[i].key, values[i])); + } + + // everything has collated correctly. Now to check the descending output + rows = db.query(queryFun, null, {descending: true}).rows; + for (i=0; i<values.length; i++) { + T(equals(rows[i].key, values[values.length - 1 -i])); + } + + // now check the key query args + for (i=1; i<values.length; i++) { + var queryOptions = {key:values[i]}; + rows = db.query(queryFun, null, queryOptions).rows; + T(rows.length == 1 && equals(rows[0].key, values[i])); + } + + // test inclusive_end=true (the default) + // the inclusive_end=true functionality is limited to endkey currently + // if you need inclusive_start=false for startkey, please do implement. ;) + var rows = db.query(queryFun, null, {endkey : "b", inclusive_end:true}).rows; + T(rows[rows.length-1].key == "b"); + // descending=true + var rows = db.query(queryFun, null, {endkey : "b", + descending:true, inclusive_end:true}).rows; + T(rows[rows.length-1].key == "b"); + + // test inclusive_end=false + var rows = db.query(queryFun, null, {endkey : "b", inclusive_end:false}).rows; + T(rows[rows.length-1].key == "aa"); + // descending=true + var rows = db.query(queryFun, null, {endkey : "b", + descending:true, inclusive_end:false}).rows; + T(rows[rows.length-1].key == "B"); + + var rows = db.query(queryFun, null, { + endkey : "b", endkey_docid: "10", + inclusive_end:false}).rows; + T(rows[rows.length-1].key == "aa"); + + var rows = db.query(queryFun, null, { + endkey : "b", endkey_docid: "11", + inclusive_end:false}).rows; + T(rows[rows.length-1].key == "b"); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_collation_raw.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_collation_raw.js b/test/javascript/tests/view_collation_raw.js new file mode 100644 index 0000000..779f7eb --- /dev/null +++ b/test/javascript/tests/view_collation_raw.js @@ -0,0 +1,130 @@ +// 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.view_collation_raw = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // NOTE, the values are already in their correct sort order. Consider this + // a specification of collation of json types. + + var values = []; + + // numbers + values.push(1); + values.push(2); + values.push(3); + values.push(4); + + values.push(false); + values.push(null); + values.push(true); + + // then object, compares each key value in the list until different. + // larger objects sort after their subset objects. + values.push({a:1}); + values.push({a:2}); + values.push({b:1}); + values.push({b:2}); + values.push({b:2, a:1}); // Member order does matter for collation. + // CouchDB preserves member order + // but doesn't require that clients will. + // (this test might fail if used with a js engine + // that doesn't preserve order) + values.push({b:2, c:2}); + + // then arrays. compared element by element until different. + // Longer arrays sort after their prefixes + values.push(["a"]); + values.push(["b"]); + values.push(["b","c"]); + values.push(["b","c", "a"]); + values.push(["b","d"]); + values.push(["b","d", "e"]); + + + // then text, case sensitive + values.push("A"); + values.push("B"); + values.push("a"); + values.push("aa"); + values.push("b"); + values.push("ba"); + values.push("bb"); + + for (var i=0; i<values.length; i++) { + db.save({_id:(i).toString(), foo:values[i]}); + } + + var designDoc = { + _id:"_design/test", // turn off couch.js id escaping? + language: "javascript", + views: { + test: {map: "function(doc) { emit(doc.foo, null); }", + options: {collation:"raw"}} + } + } + T(db.save(designDoc).ok); + + // Confirm that everything collates correctly. + var rows = db.view("test/test").rows; + for (i=0; i<values.length; i++) { + T(equals(rows[i].key, values[i])); + } + + // Confirm that couch allows raw semantics in key ranges. + rows = db.view("test/test", {startkey:"Z", endkey:"a"}).rows; + TEquals(1, rows.length); + TEquals("a", rows[0].key); + + // Check the descending output. + rows = db.view("test/test", {descending: true}).rows; + for (i=0; i<values.length; i++) { + T(equals(rows[i].key, values[values.length - 1 -i])); + } + + // now check the key query args + for (i=1; i<values.length; i++) { + rows = db.view("test/test", {key:values[i]}).rows; + T(rows.length == 1 && equals(rows[0].key, values[i])); + } + + // test inclusive_end=true (the default) + // the inclusive_end=true functionality is limited to endkey currently + // if you need inclusive_start=false for startkey, please do implement. ;) + var rows = db.view("test/test", {endkey : "b", inclusive_end:true}).rows; + T(rows[rows.length-1].key == "b"); + // descending=true + var rows = db.view("test/test", {endkey : "b", + descending:true, inclusive_end:true}).rows; + T(rows[rows.length-1].key == "b"); + + // test inclusive_end=false + var rows = db.view("test/test", {endkey : "b", inclusive_end:false}).rows; + T(rows[rows.length-1].key == "aa"); + // descending=true + var rows = db.view("test/test", {endkey : "b", + descending:true, inclusive_end:false}).rows; + T(rows[rows.length-1].key == "ba"); + + var rows = db.view("test/test", { + endkey : "b", endkey_docid: "10", + inclusive_end:false}).rows; + T(rows[rows.length-1].key == "aa"); + + var rows = db.view("test/test", { + endkey : "b", endkey_docid: "11", + inclusive_end:false}).rows; + T(rows[rows.length-1].key == "aa"); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_compaction.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_compaction.js b/test/javascript/tests/view_compaction.js new file mode 100644 index 0000000..35d6276 --- /dev/null +++ b/test/javascript/tests/view_compaction.js @@ -0,0 +1,110 @@ +// 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.view_compaction = function(debug) { + + if (debug) debugger; + + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit": "true"}); + + db.deleteDb(); + db.createDb(); + + var ddoc = { + _id: "_design/foo", + language: "javascript", + views: { + view1: { + map: "function(doc) { emit(doc._id, doc.value) }" + }, + view2: { + map: "function(doc) { if (typeof(doc.integer) === 'number') {emit(doc._id, doc.integer);} }", + reduce: "function(keys, values, rereduce) { return sum(values); }" + } + } + }; + T(db.save(ddoc).ok); + + var docs = makeDocs(0, 10000); + db.bulkSave(docs); + + var resp = db.view('foo/view1', {}); + TEquals(10000, resp.rows.length); + + resp = db.view('foo/view2', {}); + TEquals(1, resp.rows.length); + + resp = db.designInfo("_design/foo"); + TEquals(10001, resp.view_index.update_seq); + + + // update docs + for (var i = 0; i < docs.length; i++) { + docs[i].integer = docs[i].integer + 1; + } + db.bulkSave(docs); + + + resp = db.view('foo/view1', {}); + TEquals(10000, resp.rows.length); + + resp = db.view('foo/view2', {}); + TEquals(1, resp.rows.length); + + resp = db.designInfo("_design/foo"); + TEquals(20001, resp.view_index.update_seq); + + + // update docs again... + for (var i = 0; i < docs.length; i++) { + docs[i].integer = docs[i].integer + 2; + } + db.bulkSave(docs); + + + resp = db.view('foo/view1', {}); + TEquals(10000, resp.rows.length); + + resp = db.view('foo/view2', {}); + TEquals(1, resp.rows.length); + + resp = db.designInfo("_design/foo"); + TEquals(30001, resp.view_index.update_seq); + + var disk_size_before_compact = resp.view_index.disk_size; + var data_size_before_compact = resp.view_index.data_size; + + TEquals("number", typeof data_size_before_compact, "data size is a number"); + T(data_size_before_compact < disk_size_before_compact, "data size < file size"); + + // compact view group + var xhr = CouchDB.request("POST", "/" + db.name + "/_design/foo/_compact"); + T(JSON.parse(xhr.responseText).ok === true); + + resp = db.designInfo("_design/foo"); + while (resp.view_index.compact_running === true) { + resp = db.designInfo("_design/foo"); + } + + + resp = db.view('foo/view1', {}); + TEquals(10000, resp.rows.length); + + resp = db.view('foo/view2', {}); + TEquals(1, resp.rows.length); + + resp = db.designInfo("_design/foo"); + TEquals(30001, resp.view_index.update_seq); + T(resp.view_index.disk_size < disk_size_before_compact); + TEquals("number", typeof resp.view_index.data_size, "data size is a number"); + T(resp.view_index.data_size < resp.view_index.disk_size, "data size < file size"); +}; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_conflicts.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_conflicts.js b/test/javascript/tests/view_conflicts.js new file mode 100644 index 0000000..96f97d5 --- /dev/null +++ b/test/javascript/tests/view_conflicts.js @@ -0,0 +1,49 @@ +// 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.view_conflicts = function(debug) { + var dbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"}); + dbA.deleteDb(); + dbA.createDb(); + var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"}); + dbB.deleteDb(); + dbB.createDb(); + if (debug) debugger; + + var docA = {_id: "foo", bar: 42}; + T(dbA.save(docA).ok); + CouchDB.replicate(dbA.name, dbB.name); + + var docB = dbB.open("foo"); + docB.bar = 43; + dbB.save(docB); + docA.bar = 41; + dbA.save(docA); + CouchDB.replicate(dbA.name, dbB.name); + + var doc = dbB.open("foo", {conflicts: true}); + T(doc._conflicts.length == 1); + var conflictRev = doc._conflicts[0]; + if (doc.bar == 41) { // A won + T(conflictRev == docB._rev); + } else { // B won + T(doc.bar == 43); + T(conflictRev == docA._rev); + } + + var results = dbB.query(function(doc) { + if (doc._conflicts) { + emit(doc._id, doc._conflicts); + } + }); + T(results.rows[0].value[0] == conflictRev); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_errors.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_errors.js b/test/javascript/tests/view_errors.js new file mode 100644 index 0000000..e8bd08e --- /dev/null +++ b/test/javascript/tests/view_errors.js @@ -0,0 +1,189 @@ +// 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.view_errors = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + run_on_modified_server( + [{section: "couchdb", + key: "os_process_timeout", + value: "500"}], + function() { + var doc = {integer: 1, string: "1", array: [1, 2, 3]}; + T(db.save(doc).ok); + + // emitting a key value that is undefined should result in that row + // being included in the view results as null + var results = db.query(function(doc) { + emit(doc.undef, null); + }); + T(results.total_rows == 1); + T(results.rows[0].key == null); + + // if a view function throws an exception, its results are not included in + // the view index, but the view does not itself raise an error + var results = db.query(function(doc) { + doc.undef(); // throws an error + }); + T(results.total_rows == 0); + + // if a view function includes an undefined value in the emitted key or + // value, it is treated as null + var results = db.query(function(doc) { + emit([doc._id, doc.undef], null); + }); + T(results.total_rows == 1); + T(results.rows[0].key[1] == null); + + // querying a view with invalid params should give a resonable error message + var xhr = CouchDB.request("POST", "/test_suite_db/_temp_view?startkey=foo", { + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({language: "javascript", + map : "function(doc){emit(doc.integer)}" + }) + }); + T(JSON.parse(xhr.responseText).error == "bad_request"); + + // content type must be json + var xhr = CouchDB.request("POST", "/test_suite_db/_temp_view", { + headers: {"Content-Type": "application/x-www-form-urlencoded"}, + body: JSON.stringify({language: "javascript", + map : "function(doc){}" + }) + }); + T(xhr.status == 415); + + var map = function (doc) {emit(doc.integer, doc.integer);}; + + try { + db.query(map, null, {group: true}); + T(0 == 1); + } catch(e) { + T(e.error == "query_parse_error"); + } + + var designDoc = { + _id:"_design/test", + language: "javascript", + views: { + "no_reduce": {map:"function(doc) {emit(doc._id, null);}"}, + "with_reduce": { + map:"function (doc) {emit(doc.integer, doc.integer)};", + reduce:"function (keys, values) { return sum(values); };"} + } + }; + T(db.save(designDoc).ok); + + var designDoc2 = { + _id:"_design/testbig", + language: "javascript", + views: { + "reduce_too_big" : { + map:"function (doc) {emit(doc.integer, doc.integer)};", + reduce:"function (keys, values) { var chars = []; for (var i=0; i < 1000; i++) {chars.push('wazzap');};return chars; };"} + } + }; + T(db.save(designDoc2).ok); + + try { + db.view("test/no_reduce", {group: true}); + T(0 == 1); + } catch(e) { + T(db.last_req.status == 400); + T(e.error == "query_parse_error"); + } + + try { + db.view("test/no_reduce", {group_level: 1}); + T(0 == 1); + } catch(e) { + T(db.last_req.status == 400); + T(e.error == "query_parse_error"); + } + + try { + db.view("test/no_reduce", {reduce: true}); + T(0 == 1); + } catch(e) { + T(db.last_req.status == 400); + T(e.error == "query_parse_error"); + } + + db.view("test/no_reduce", {reduce: false}); + TEquals(200, db.last_req.status, "reduce=false for map views (without" + + " group or group_level) is allowed"); + + try { + db.view("test/with_reduce", {group: true, reduce: false}); + T(0 == 1); + } catch(e) { + T(db.last_req.status == 400); + T(e.error == "query_parse_error"); + } + + try { + db.view("test/with_reduce", {group_level: 1, reduce: false}); + T(0 == 1); + } catch(e) { + T(db.last_req.status == 400); + T(e.error == "query_parse_error"); + } + + var designDoc3 = { + _id:"_design/infinite", + language: "javascript", + views: { + "infinite_loop" :{map:"function(doc) {while(true){emit(doc,doc);}};"} + } + }; + T(db.save(designDoc3).ok); + + try { + db.view("infinite/infinite_loop"); + T(0 == 1); + } catch(e) { + T(e.error == "os_process_error"); + } + + // Check error responses for invalid multi-get bodies. + var path = "/test_suite_db/_design/test/_view/no_reduce"; + var xhr = CouchDB.request("POST", path, {body: "[]"}); + T(xhr.status == 400); + result = JSON.parse(xhr.responseText); + T(result.error == "bad_request"); + T(result.reason == "Request body must be a JSON object"); + var data = "{\"keys\": 1}"; + xhr = CouchDB.request("POST", path, {body:data}); + T(xhr.status == 400); + result = JSON.parse(xhr.responseText); + T(result.error == "bad_request"); + T(result.reason == "`keys` member must be a array."); + + // if the reduce grows to fast, throw an overflow error + var path = "/test_suite_db/_design/testbig/_view/reduce_too_big"; + xhr = CouchDB.request("GET", path); + T(xhr.status == 500); + result = JSON.parse(xhr.responseText); + T(result.error == "reduce_overflow_error"); + + try { + db.query(function() {emit(null, null)}, null, {startkey: 2, endkey:1}); + T(0 == 1); + } catch(e) { + T(e.error == "query_parse_error"); + T(e.reason.match(/no rows can match/i)); + } + }); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_include_docs.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_include_docs.js b/test/javascript/tests/view_include_docs.js new file mode 100644 index 0000000..dab79b8 --- /dev/null +++ b/test/javascript/tests/view_include_docs.js @@ -0,0 +1,192 @@ +// 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.view_include_docs = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var docs = makeDocs(0, 100); + db.bulkSave(docs); + + var designDoc = { + _id:"_design/test", + language: "javascript", + views: { + all_docs: { + map: "function(doc) { emit(doc.integer, doc.string) }" + }, + with_prev: { + map: "function(doc){if(doc.prev) emit(doc._id,{'_rev':doc.prev}); else emit(doc._id,{'_rev':doc._rev});}" + }, + with_id: { + map: "function(doc) {if(doc.link_id) { var value = {'_id':doc.link_id}; if (doc.link_rev) {value._rev = doc.link_rev}; emit(doc._id, value);}};" + }, + summate: { + map:"function (doc) { if (typeof doc.integer === 'number') {emit(doc.integer, doc.integer)};}", + reduce:"function (keys, values) { return sum(values); };" + } + } + } + T(db.save(designDoc).ok); + + var resp = db.view('test/all_docs', {include_docs: true, limit: 2}); + T(resp.rows.length == 2); + T(resp.rows[0].id == "0"); + T(resp.rows[0].doc._id == "0"); + T(resp.rows[1].id == "1"); + T(resp.rows[1].doc._id == "1"); + + resp = db.view('test/all_docs', {include_docs: true}, [29, 74]); + T(resp.rows.length == 2); + T(resp.rows[0].doc._id == "29"); + T(resp.rows[1].doc.integer == 74); + + resp = db.allDocs({limit: 2, skip: 1, include_docs: true}); + T(resp.rows.length == 2); + T(resp.rows[0].doc.integer == 1); + T(resp.rows[1].doc.integer == 10); + + resp = db.allDocs({include_docs: true}, ['not_a_doc']); + T(resp.rows.length == 1); + T(!resp.rows[0].doc); + + resp = db.allDocs({include_docs: true}, ["1", "foo"]); + T(resp.rows.length == 2); + T(resp.rows[0].doc.integer == 1); + T(!resp.rows[1].doc); + + resp = db.allDocs({include_docs: true, limit: 0}); + T(resp.rows.length == 0); + + // No reduce support + try { + resp = db.view('test/summate', {include_docs: true}); + alert(JSON.stringify(resp)); + T(0==1); + } catch (e) { + T(e.error == 'query_parse_error'); + } + + // Reduce support when reduce=false + resp = db.view('test/summate', {reduce: false, include_docs: true}); + T(resp.rows.length == 100); + + // Not an error with include_docs=false&reduce=true + resp = db.view('test/summate', {reduce: true, include_docs: false}); + T(resp.rows.length == 1); + T(resp.rows[0].value == 4950); + + T(db.save({ + "_id": "link-to-10", + "link_id" : "10" + }).ok); + + // you can link to another doc from a value. + resp = db.view("test/with_id", {key:"link-to-10"}); + T(resp.rows[0].key == "link-to-10"); + T(resp.rows[0].value["_id"] == "10"); + + resp = db.view("test/with_id", {key:"link-to-10",include_docs: true}); + T(resp.rows[0].key == "link-to-10"); + T(resp.rows[0].value["_id"] == "10"); + T(resp.rows[0].doc._id == "10"); + + // Check emitted _rev controls things + resp = db.allDocs({include_docs: true}, ["0"]); + var before = resp.rows[0].doc; + + var after = db.open("0"); + after.integer = 100; + after.prev = after._rev; + resp = db.save(after) + T(resp.ok); + + var after = db.open("0"); + TEquals(resp.rev, after._rev, "fails with firebug running"); + T(after._rev != after.prev, "passes"); + TEquals(100, after.integer, "fails with firebug running"); + + // should emit the previous revision + resp = db.view("test/with_prev", {include_docs: true}, ["0"]); + T(resp.rows[0].doc._id == "0"); + T(resp.rows[0].doc._rev == before._rev); + T(!resp.rows[0].doc.prev); + T(resp.rows[0].doc.integer == 0); + + var xhr = CouchDB.request("POST", "/test_suite_db/_compact"); + T(xhr.status == 202) + while (db.info().compact_running) {} + + resp = db.view("test/with_prev", {include_docs: true}, ["0", "23"]); + T(resp.rows.length == 2); + T(resp.rows[0].key == "0"); + T(resp.rows[0].id == "0"); + T(!resp.rows[0].doc); + T(resp.rows[0].doc == null); + T(resp.rows[1].doc.integer == 23); + + // COUCHDB-549 - include_docs=true with conflicts=true + + var dbA = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"}); + var dbB = new CouchDB("test_suite_db_b", {"X-Couch-Full-Commit":"false"}); + + dbA.deleteDb(); + dbA.createDb(); + dbB.deleteDb(); + dbB.createDb(); + + var ddoc = { + _id: "_design/mydesign", + language : "javascript", + views : { + myview : { + map: (function(doc) { + emit(doc.value, 1); + }).toString() + } + } + }; + TEquals(true, dbA.save(ddoc).ok); + + var doc1a = {_id: "foo", value: 1, str: "1"}; + TEquals(true, dbA.save(doc1a).ok); + + var doc1b = {_id: "foo", value: 1, str: "666"}; + TEquals(true, dbB.save(doc1b).ok); + + var doc2 = {_id: "bar", value: 2, str: "2"}; + TEquals(true, dbA.save(doc2).ok); + + TEquals(true, CouchDB.replicate(dbA.name, dbB.name).ok); + + doc1b = dbB.open("foo", {conflicts: true}); + TEquals(true, doc1b._conflicts instanceof Array); + TEquals(1, doc1b._conflicts.length); + var conflictRev = doc1b._conflicts[0]; + + doc2 = dbB.open("bar", {conflicts: true}); + TEquals("undefined", typeof doc2._conflicts); + + resp = dbB.view("mydesign/myview", {include_docs: true, conflicts: true}); + + TEquals(2, resp.rows.length); + TEquals(true, resp.rows[0].doc._conflicts instanceof Array); + TEquals(1, resp.rows[0].doc._conflicts.length); + TEquals(conflictRev, resp.rows[0].doc._conflicts[0]); + TEquals("undefined", typeof resp.rows[1].doc._conflicts); + + // cleanup + dbA.deleteDb(); + dbB.deleteDb(); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_multi_key_all_docs.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_multi_key_all_docs.js b/test/javascript/tests/view_multi_key_all_docs.js new file mode 100644 index 0000000..7c7f6f8 --- /dev/null +++ b/test/javascript/tests/view_multi_key_all_docs.js @@ -0,0 +1,95 @@ +// 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.view_multi_key_all_docs = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var docs = makeDocs(0, 100); + db.bulkSave(docs); + + var keys = ["10","15","30","37","50"]; + var rows = db.allDocs({},keys).rows; + T(rows.length == keys.length); + for(var i=0; i<rows.length; i++) + T(rows[i].id == keys[i]); + + // keys in GET parameters + rows = db.allDocs({keys:keys}, null).rows; + T(rows.length == keys.length); + for(var i=0; i<rows.length; i++) + T(rows[i].id == keys[i]); + + rows = db.allDocs({limit: 1}, keys).rows; + T(rows.length == 1); + T(rows[0].id == keys[0]); + + // keys in GET parameters + rows = db.allDocs({limit: 1, keys: keys}, null).rows; + T(rows.length == 1); + T(rows[0].id == keys[0]); + + rows = db.allDocs({skip: 2}, keys).rows; + T(rows.length == 3); + for(var i=0; i<rows.length; i++) + T(rows[i].id == keys[i+2]); + + // keys in GET parameters + rows = db.allDocs({skip: 2, keys: keys}, null).rows; + T(rows.length == 3); + for(var i=0; i<rows.length; i++) + T(rows[i].id == keys[i+2]); + + rows = db.allDocs({descending: "true"}, keys).rows; + T(rows.length == keys.length); + for(var i=0; i<rows.length; i++) + T(rows[i].id == keys[keys.length-i-1]); + + // keys in GET parameters + rows = db.allDocs({descending: "true", keys: keys}, null).rows; + T(rows.length == keys.length); + for(var i=0; i<rows.length; i++) + T(rows[i].id == keys[keys.length-i-1]); + + rows = db.allDocs({descending: "true", skip: 3, limit:1}, keys).rows; + T(rows.length == 1); + T(rows[0].id == keys[1]); + + // keys in GET parameters + rows = db.allDocs({descending: "true", skip: 3, limit:1, keys: keys}, null).rows; + T(rows.length == 1); + T(rows[0].id == keys[1]); + + // Check we get invalid rows when the key doesn't exist + rows = db.allDocs({}, [1, "i_dont_exist", "0"]).rows; + T(rows.length == 3); + T(rows[0].error == "not_found"); + T(!rows[0].id); + T(rows[1].error == "not_found"); + T(!rows[1].id); + T(rows[2].id == rows[2].key && rows[2].key == "0"); + + // keys in GET parameters + rows = db.allDocs({keys: [1, "i_dont_exist", "0"]}, null).rows; + T(rows.length == 3); + T(rows[0].error == "not_found"); + T(!rows[0].id); + T(rows[1].error == "not_found"); + T(!rows[1].id); + T(rows[2].id == rows[2].key && rows[2].key == "0"); + + // empty keys + rows = db.allDocs({keys: []}, null).rows; + T(rows.length == 0); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_multi_key_design.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_multi_key_design.js b/test/javascript/tests/view_multi_key_design.js new file mode 100644 index 0000000..a84d07a --- /dev/null +++ b/test/javascript/tests/view_multi_key_design.js @@ -0,0 +1,220 @@ +// 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.view_multi_key_design = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var docs = makeDocs(0, 100); + db.bulkSave(docs); + + var designDoc = { + _id:"_design/test", + language: "javascript", + views: { + all_docs: { + map: "function(doc) { emit(doc.integer, doc.string) }" + }, + multi_emit: { + map: "function(doc) {for(var i = 0 ; i < 3 ; i++) { emit(i, doc.integer) ; } }" + }, + summate: { + map:"function (doc) {emit(doc.integer, doc.integer)};", + reduce:"function (keys, values) { return sum(values); };" + } + } + }; + T(db.save(designDoc).ok); + + // Test that missing keys work too + var keys = [101,30,15,37,50]; + var reduce = db.view("test/summate",{group:true},keys).rows; + T(reduce.length == keys.length-1); // 101 is missing + for(var i=0; i<reduce.length; i++) { + T(keys.indexOf(reduce[i].key) != -1); + T(reduce[i].key == reduce[i].value); + } + + // First, the goods: + var keys = [10,15,30,37,50]; + var rows = db.view("test/all_docs",{},keys).rows; + for(var i=0; i<rows.length; i++) { + T(keys.indexOf(rows[i].key) != -1); + T(rows[i].key == rows[i].value); + } + + // with GET keys + rows = db.view("test/all_docs",{keys:keys},null).rows; + for(var i=0;i<rows.length; i++) { + T(keys.indexOf(rows[i].key) != -1); + T(rows[i].key == rows[i].value); + } + + // with empty keys + rows = db.view("test/all_docs",{keys:[]},null).rows; + T(rows.length == 0); + + var reduce = db.view("test/summate",{group:true},keys).rows; + T(reduce.length == keys.length); + for(var i=0; i<reduce.length; i++) { + T(keys.indexOf(reduce[i].key) != -1); + T(reduce[i].key == reduce[i].value); + } + + // with GET keys + reduce = db.view("test/summate",{group:true,keys:keys},null).rows; + T(reduce.length == keys.length); + for(var i=0; i<reduce.length; i++) { + T(keys.indexOf(reduce[i].key) != -1); + T(reduce[i].key == reduce[i].value); + } + + // Test that invalid parameter combinations get rejected + var badargs = [{startkey:0}, {endkey:0}, {key: 0}, {group_level: 2}]; + var getbadargs = [{startkey:0, keys:keys}, {endkey:0, keys:keys}, + {key:0, keys:keys}, {group_level: 2, keys:keys}]; + for(var i in badargs) + { + try { + db.view("test/all_docs",badargs[i],keys); + T(0==1); + } catch (e) { + T(e.error == "query_parse_error"); + } + + try { + db.view("test/all_docs",getbadargs[i],null); + T(0==1); + } catch (e) { + T(e.error = "query_parse_error"); + } + } + + try { + db.view("test/summate",{},keys); + T(0==1); + } catch (e) { + T(e.error == "query_parse_error"); + } + + try { + db.view("test/summate",{keys:keys},null); + T(0==1); + } catch (e) { + T(e.error == "query_parse_error"); + } + + // Test that a map & reduce containing func support keys when reduce=false + var resp = db.view("test/summate", {reduce: false}, keys); + T(resp.rows.length == 5); + + resp = db.view("test/summate", {reduce: false, keys: keys}, null); + T(resp.rows.length == 5); + + // Check that limiting by startkey_docid and endkey_docid get applied + // as expected. + var curr = db.view("test/multi_emit", {startkey_docid: 21, endkey_docid: 23}, [0, 2]).rows; + var exp_key = [ 0, 0, 0, 2, 2, 2] ; + var exp_val = [21, 22, 23, 21, 22, 23] ; + T(curr.length == 6); + for( var i = 0 ; i < 6 ; i++) + { + T(curr[i].key == exp_key[i]); + T(curr[i].value == exp_val[i]); + } + + curr = db.view("test/multi_emit", {startkey_docid: 21, endkey_docid: 23, keys: [0, 2]}, null).rows; + T(curr.length == 6); + for( var i = 0 ; i < 6 ; i++) + { + T(curr[i].key == exp_key[i]); + T(curr[i].value == exp_val[i]); + } + + // Check limit works + curr = db.view("test/all_docs", {limit: 1}, keys).rows; + T(curr.length == 1); + T(curr[0].key == 10); + + curr = db.view("test/all_docs", {limit: 1, keys: keys}, null).rows; + T(curr.length == 1); + T(curr[0].key == 10); + + // Check offset works + curr = db.view("test/multi_emit", {skip: 1}, [0]).rows; + T(curr.length == 99); + T(curr[0].value == 1); + + curr = db.view("test/multi_emit", {skip: 1, keys: [0]}, null).rows; + T(curr.length == 99); + T(curr[0].value == 1); + + // Check that dir works + curr = db.view("test/multi_emit", {descending: "true"}, [1]).rows; + T(curr.length == 100); + T(curr[0].value == 99); + T(curr[99].value == 0); + + curr = db.view("test/multi_emit", {descending: "true", keys: [1]}, null).rows; + T(curr.length == 100); + T(curr[0].value == 99); + T(curr[99].value == 0); + + // Check a couple combinations + curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2}, [2]).rows; + T(curr.length, 2); + T(curr[0].value == 96); + T(curr[1].value == 95); + + curr = db.view("test/multi_emit", {descending: "true", skip: 3, limit: 2, keys: [2]}, null).rows; + T(curr.length, 2); + T(curr[0].value == 96); + T(curr[1].value == 95); + + curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13"}, [0]).rows; + T(curr.length == 3); + T(curr[0].value == 15); + T(curr[1].value == 16); + T(curr[2].value == 17); + + curr = db.view("test/multi_emit", {skip: 2, limit: 3, startkey_docid: "13", keys: [0]}, null).rows; + T(curr.length == 3); + T(curr[0].value == 15); + T(curr[1].value == 16); + T(curr[2].value == 17); + + curr = db.view("test/multi_emit", + {skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27"}, [1]).rows; + T(curr.length == 2); + T(curr[0].value == 26); + T(curr[1].value == 27); + + curr = db.view("test/multi_emit", + {skip: 1, limit: 5, startkey_docid: "25", endkey_docid: "27", keys: [1]}, null).rows; + T(curr.length == 2); + T(curr[0].value == 26); + T(curr[1].value == 27); + + curr = db.view("test/multi_emit", + {skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true"}, [1]).rows; + T(curr.length == 2); + T(curr[0].value == 27); + T(curr[1].value == 26); + + curr = db.view("test/multi_emit", + {skip: 1, limit: 5, startkey_docid: "28", endkey_docid: "26", descending: "true", keys: [1]}, null).rows; + T(curr.length == 2); + T(curr[0].value == 27); + T(curr[1].value == 26); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_multi_key_temp.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_multi_key_temp.js b/test/javascript/tests/view_multi_key_temp.js new file mode 100644 index 0000000..3c05409 --- /dev/null +++ b/test/javascript/tests/view_multi_key_temp.js @@ -0,0 +1,40 @@ +// 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.view_multi_key_temp = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var docs = makeDocs(0, 100); + db.bulkSave(docs); + + var queryFun = function(doc) { emit(doc.integer, doc.integer) }; + var reduceFun = function (keys, values) { return sum(values); }; + + var keys = [10,15,30,37,50]; + var rows = db.query(queryFun, null, {}, keys).rows; + for(var i=0; i<rows.length; i++) { + T(keys.indexOf(rows[i].key) != -1); + T(rows[i].key == rows[i].value); + } + + var reduce = db.query(queryFun, reduceFun, {group:true}, keys).rows; + for(var i=0; i<reduce.length; i++) { + T(keys.indexOf(reduce[i].key) != -1); + T(reduce[i].key == reduce[i].value); + } + + rows = db.query(queryFun, null, {}, []).rows; + T(rows.length == 0); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_offsets.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_offsets.js b/test/javascript/tests/view_offsets.js new file mode 100644 index 0000000..464a1ae --- /dev/null +++ b/test/javascript/tests/view_offsets.js @@ -0,0 +1,108 @@ +// 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.view_offsets = function(debug) { + if (debug) debugger; + + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + + var designDoc = { + _id : "_design/test", + views : { + offset : { + map : "function(doc) { emit([doc.letter, doc.number], doc); }", + } + } + }; + T(db.save(designDoc).ok); + + var docs = [ + {_id : "a1", letter : "a", number : 1, foo: "bar"}, + {_id : "a2", letter : "a", number : 2, foo: "bar"}, + {_id : "a3", letter : "a", number : 3, foo: "bar"}, + {_id : "b1", letter : "b", number : 1, foo: "bar"}, + {_id : "b2", letter : "b", number : 2, foo: "bar"}, + {_id : "b3", letter : "b", number : 3, foo: "bar"}, + {_id : "b4", letter : "b", number : 4, foo: "bar"}, + {_id : "b5", letter : "b", number : 5, foo: "bar"}, + {_id : "c1", letter : "c", number : 1, foo: "bar"}, + {_id : "c2", letter : "c", number : 2, foo: "bar"}, + ]; + db.bulkSave(docs); + + var check = function(startkey, offset) { + var opts = {startkey: startkey, descending: true}; + T(db.view("test/offset", opts).offset == offset); + }; + + [ + [["c", 2], 0], + [["c", 1], 1], + [["b", 5], 2], + [["b", 4], 3], + [["b", 3], 4], + [["b", 2], 5], + [["b", 1], 6], + [["a", 3], 7], + [["a", 2], 8], + [["a", 1], 9] + ].forEach(function(row){ check(row[0], row[1]);}); + + var runTest = function () { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + + var designDoc = { + _id : "_design/test", + views : { + offset : { + map : "function(doc) { emit([doc.letter, doc.number], doc);}", + } + } + }; + T(db.save(designDoc).ok); + + var docs = [ + {_id : "a1", letter : "a", number : 1, foo : "bar"}, + {_id : "a2", letter : "a", number : 2, foo : "bar"}, + {_id : "a3", letter : "a", number : 3, foo : "bar"}, + {_id : "b1", letter : "b", number : 1, foo : "bar"}, + {_id : "b2", letter : "b", number : 2, foo : "bar"}, + {_id : "b3", letter : "b", number : 3, foo : "bar"}, + {_id : "b4", letter : "b", number : 4, foo : "bar"}, + {_id : "b5", letter : "b", number : 5, foo : "bar"}, + {_id : "c1", letter : "c", number : 1, foo : "bar"}, + {_id : "c2", letter : "c", number : 2, foo : "bar"} + ]; + db.bulkSave(docs); + + var res1 = db.view("test/offset", { + startkey: ["b",4], startkey_docid: "b4", endkey: ["b"], + limit: 2, descending: true, skip: 1 + }) + + var res2 = db.view("test/offset", {startkey: ["c", 3]}); + var res3 = db.view("test/offset", { + startkey: ["b", 6], + endkey: ["b", 7] + }); + + return res1.offset == 4 && res2.offset == docs.length && res3.offset == 8; + + }; + + for(var i = 0; i < 15; i++) T(runTest()); +} + http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_pagination.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_pagination.js b/test/javascript/tests/view_pagination.js new file mode 100644 index 0000000..ed3a7ee --- /dev/null +++ b/test/javascript/tests/view_pagination.js @@ -0,0 +1,147 @@ +// 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.view_pagination = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var docs = makeDocs(0, 100); + db.bulkSave(docs); + + var queryFun = function(doc) { emit(doc.integer, null); }; + var i; + + // page through the view ascending + for (i = 0; i < docs.length; i += 10) { + var queryResults = db.query(queryFun, null, { + startkey: i, + startkey_docid: i, + limit: 10 + }); + T(queryResults.rows.length == 10); + T(queryResults.total_rows == docs.length); + T(queryResults.offset == i); + var j; + for (j = 0; j < 10;j++) { + T(queryResults.rows[j].key == i + j); + } + + // test aliases start_key and start_key_doc_id + queryResults = db.query(queryFun, null, { + start_key: i, + start_key_doc_id: i, + limit: 10 + }); + T(queryResults.rows.length == 10); + T(queryResults.total_rows == docs.length); + T(queryResults.offset == i); + for (j = 0; j < 10;j++) { + T(queryResults.rows[j].key == i + j); + } + } + + // page through the view descending + for (i = docs.length - 1; i >= 0; i -= 10) { + var queryResults = db.query(queryFun, null, { + startkey: i, + startkey_docid: i, + descending: true, + limit: 10 + }); + T(queryResults.rows.length == 10); + T(queryResults.total_rows == docs.length); + T(queryResults.offset == docs.length - i - 1); + var j; + for (j = 0; j < 10; j++) { + T(queryResults.rows[j].key == i - j); + } + } + + // ignore decending=false. CouchDB should just ignore that. + for (i = 0; i < docs.length; i += 10) { + var queryResults = db.query(queryFun, null, { + startkey: i, + startkey_docid: i, + descending: false, + limit: 10 + }); + T(queryResults.rows.length == 10); + T(queryResults.total_rows == docs.length); + T(queryResults.offset == i); + var j; + for (j = 0; j < 10;j++) { + T(queryResults.rows[j].key == i + j); + } + } + + function testEndkeyDocId(queryResults) { + T(queryResults.rows.length == 35); + T(queryResults.total_rows == docs.length); + T(queryResults.offset == 1); + T(queryResults.rows[0].id == "1"); + T(queryResults.rows[1].id == "10"); + T(queryResults.rows[2].id == "11"); + T(queryResults.rows[3].id == "12"); + T(queryResults.rows[4].id == "13"); + T(queryResults.rows[5].id == "14"); + T(queryResults.rows[6].id == "15"); + T(queryResults.rows[7].id == "16"); + T(queryResults.rows[8].id == "17"); + T(queryResults.rows[9].id == "18"); + T(queryResults.rows[10].id == "19"); + T(queryResults.rows[11].id == "2"); + T(queryResults.rows[12].id == "20"); + T(queryResults.rows[13].id == "21"); + T(queryResults.rows[14].id == "22"); + T(queryResults.rows[15].id == "23"); + T(queryResults.rows[16].id == "24"); + T(queryResults.rows[17].id == "25"); + T(queryResults.rows[18].id == "26"); + T(queryResults.rows[19].id == "27"); + T(queryResults.rows[20].id == "28"); + T(queryResults.rows[21].id == "29"); + T(queryResults.rows[22].id == "3"); + T(queryResults.rows[23].id == "30"); + T(queryResults.rows[24].id == "31"); + T(queryResults.rows[25].id == "32"); + T(queryResults.rows[26].id == "33"); + T(queryResults.rows[27].id == "34"); + T(queryResults.rows[28].id == "35"); + T(queryResults.rows[29].id == "36"); + T(queryResults.rows[30].id == "37"); + T(queryResults.rows[31].id == "38"); + T(queryResults.rows[32].id == "39"); + T(queryResults.rows[33].id == "4"); + T(queryResults.rows[34].id == "40"); + } + + // test endkey_docid + var queryResults = db.query(function(doc) { emit(null, null); }, null, { + startkey: null, + startkey_docid: 1, + endkey: null, + endkey_docid: 40 + }); + testEndkeyDocId(queryResults); + + // test aliases end_key_doc_id and end_key + queryResults = db.query(function(doc) { emit(null, null); }, null, { + start_key: null, + start_key_doc_id: 1, + end_key: null, + end_key_doc_id: 40 + }); + testEndkeyDocId(queryResults); + + }; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_sandboxing.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_sandboxing.js b/test/javascript/tests/view_sandboxing.js new file mode 100644 index 0000000..5c73c5a --- /dev/null +++ b/test/javascript/tests/view_sandboxing.js @@ -0,0 +1,140 @@ +// 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.view_sandboxing = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var doc = {integer: 1, string: "1", array: [1, 2, 3]}; + T(db.save(doc).ok); +/* + // make sure that attempting to change the document throws an error + var results = db.query(function(doc) { + doc.integer = 2; + emit(null, doc); + }); + T(results.total_rows == 0); + + var results = db.query(function(doc) { + doc.array[0] = 0; + emit(null, doc); + }); + T(results.total_rows == 0); +*/ + // make sure that a view cannot invoke interpreter internals such as the + // garbage collector + var results = db.query(function(doc) { + gc(); + emit(null, doc); + }); + T(results.total_rows == 0); + + // make sure that a view cannot access the map_funs array defined used by + // the view server + var results = db.query(function(doc) { map_funs.push(1); emit(null, doc); }); + T(results.total_rows == 0); + + // make sure that a view cannot access the map_results array defined used by + // the view server + var results = db.query(function(doc) { map_results.push(1); emit(null, doc); }); + T(results.total_rows == 0); + + // test for COUCHDB-925 + // altering 'doc' variable in map function affects other map functions + var ddoc = { + _id: "_design/foobar", + language: "javascript", + views: { + view1: { + map: + (function(doc) { + if (doc.values) { + doc.values = [666]; + } + if (doc.tags) { + doc.tags.push("qwerty"); + } + if (doc.tokens) { + doc.tokens["c"] = 3; + } + }).toString() + }, + view2: { + map: + (function(doc) { + if (doc.values) { + emit(doc._id, doc.values); + } + if (doc.tags) { + emit(doc._id, doc.tags); + } + if (doc.tokens) { + emit(doc._id, doc.tokens); + } + }).toString() + } + } + }; + var doc1 = { + _id: "doc1", + values: [1, 2, 3] + }; + var doc2 = { + _id: "doc2", + tags: ["foo", "bar"], + tokens: {a: 1, b: 2} + }; + + db.deleteDb(); + db.createDb(); + T(db.save(ddoc).ok); + T(db.save(doc1).ok); + T(db.save(doc2).ok); + + var view1Results = db.view( + "foobar/view1", {bypass_cache: Math.round(Math.random() * 1000)}); + var view2Results = db.view( + "foobar/view2", {bypass_cache: Math.round(Math.random() * 1000)}); + + TEquals(0, view1Results.rows.length, "view1 has 0 rows"); + TEquals(3, view2Results.rows.length, "view2 has 3 rows"); + + TEquals(doc1._id, view2Results.rows[0].key); + TEquals(doc2._id, view2Results.rows[1].key); + TEquals(doc2._id, view2Results.rows[2].key); + + // https://bugzilla.mozilla.org/show_bug.cgi?id=449657 + TEquals(3, view2Results.rows[0].value.length, + "Warning: installed SpiderMonkey version doesn't allow sealing of arrays"); + if (view2Results.rows[0].value.length === 3) { + TEquals(1, view2Results.rows[0].value[0]); + TEquals(2, view2Results.rows[0].value[1]); + TEquals(3, view2Results.rows[0].value[2]); + } + + TEquals(1, view2Results.rows[1].value["a"]); + TEquals(2, view2Results.rows[1].value["b"]); + TEquals('undefined', typeof view2Results.rows[1].value["c"], + "doc2.tokens object was not sealed"); + + TEquals(2, view2Results.rows[2].value.length, + "Warning: installed SpiderMonkey version doesn't allow sealing of arrays"); + if (view2Results.rows[2].value.length === 2) { + TEquals("foo", view2Results.rows[2].value[0]); + TEquals("bar", view2Results.rows[2].value[1]); + } + + // cleanup + db.deleteDb(); +}; http://git-wip-us.apache.org/repos/asf/couchdb/blob/013ee66e/test/javascript/tests/view_update_seq.js ---------------------------------------------------------------------- diff --git a/test/javascript/tests/view_update_seq.js b/test/javascript/tests/view_update_seq.js new file mode 100644 index 0000000..df92b11 --- /dev/null +++ b/test/javascript/tests/view_update_seq.js @@ -0,0 +1,106 @@ +// 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.view_update_seq = function(debug) { + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"true"}); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + T(db.info().update_seq == 0); + + var resp = db.allDocs({update_seq:true}); + + T(resp.rows.length == 0); + T(resp.update_seq == 0); + + var designDoc = { + _id:"_design/test", + language: "javascript", + views: { + all_docs: { + map: "function(doc) { emit(doc.integer, doc.string) }" + }, + summate: { + map:"function (doc) { if (typeof doc.integer === 'number') { emit(doc.integer, doc.integer)}; }", + reduce:"function (keys, values) { return sum(values); };" + } + } + }; + T(db.save(designDoc).ok); + + T(db.info().update_seq == 1); + + resp = db.allDocs({update_seq:true}); + + T(resp.rows.length == 1); + T(resp.update_seq == 1); + + var docs = makeDocs(0, 100); + db.bulkSave(docs); + + resp = db.allDocs({limit: 1}); + T(resp.rows.length == 1); + T(!resp.update_seq, "all docs"); + + resp = db.allDocs({limit: 1, update_seq:true}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + resp = db.view('test/all_docs', {limit: 1, update_seq:true}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + resp = db.view('test/all_docs', {limit: 1, update_seq:false}); + T(resp.rows.length == 1); + T(!resp.update_seq, "view"); + + resp = db.view('test/summate', {update_seq:true}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + db.save({"id":"0", "integer": 1}); + resp = db.view('test/all_docs', {limit: 1,stale: "ok", update_seq:true}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + db.save({"id":"00", "integer": 2}); + resp = db.view('test/all_docs', + {limit: 1, stale: "update_after", update_seq: true}); + T(resp.rows.length == 1); + T(resp.update_seq == 101); + + // wait 5 seconds for the next assertions to pass in very slow machines + var t0 = new Date(), t1; + do { + CouchDB.request("GET", "/"); + t1 = new Date(); + } while ((t1 - t0) < 5000); + + resp = db.view('test/all_docs', {limit: 1, stale: "ok", update_seq: true}); + T(resp.rows.length == 1); + T(resp.update_seq == 103); + + resp = db.view('test/all_docs', {limit: 1, update_seq:true}); + T(resp.rows.length == 1); + T(resp.update_seq == 103); + + resp = db.view('test/all_docs',{update_seq:true},["0","1"]); + T(resp.update_seq == 103); + + resp = db.view('test/all_docs',{update_seq:true},["0","1"]); + T(resp.update_seq == 103); + + resp = db.view('test/summate',{group:true, update_seq:true},[0,1]); + TEquals(103, resp.update_seq); + +};