// 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.design_docs = function(debug) { var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"}); var db2 = new CouchDB("test_suite_db_a", {"X-Couch-Full-Commit":"false"}); if (debug) debugger; db.deleteDb(); db.createDb(); db2.deleteDb(); db2.createDb(); var server_config = [ { section: "query_server_config", key: "reduce_limit", value: "false" } ]; var testFun = function() { var numDocs = 500; function makebigstring(power) { var str = "a"; while(power-- > 0) { str = str + str; } return str; } var designDoc = { _id: "_design/test", language: "javascript", whatever : { stringzone : "exports.string = 'plankton';", commonjs : { whynot : "exports.test = require('../stringzone'); " + "exports.foo = require('whatever/stringzone');", upper : "exports.testing = require('./whynot').test.string.toUpperCase()+" + "module.id+require('./whynot').foo.string", circular_one: "require('./circular_two'); exports.name = 'One';", circular_two: "require('./circular_one'); exports.name = 'Two';" }, // paths relative to parent idtest1: { a: { b: {d: "module.exports = require('../c/e').id;"}, c: {e: "exports.id = module.id;"} } }, // multiple paths relative to parent idtest2: { a: { b: {d: "module.exports = require('../../a/c/e').id;"}, c: {e: "exports.id = module.id;"} } }, // paths relative to module idtest3: { a: { b: "module.exports = require('./c/d').id;", c: { d: "module.exports = require('./e');", e: "exports.id = module.id;" } } }, // paths relative to module and parent idtest4: { a: { b: "module.exports = require('../a/./c/d').id;", c: { d: "module.exports = require('./e');", e: "exports.id = module.id;" } } }, // paths relative to root idtest5: { a: "module.exports = require('whatever/idtest5/b').id;", b: "exports.id = module.id;" } }, views: { all_docs_twice: { map: (function(doc) { emit(doc.integer, null); emit(doc.integer, null); }).toString() }, no_docs: { map: (function(doc) { }).toString() }, single_doc: { map: (function(doc) { if (doc._id === "1") { emit(1, null); } }).toString() }, summate: { map: (function(doc) { emit(doc.integer, doc.integer); }).toString(), reduce: (function(keys, values) { return sum(values); }).toString() }, summate2: { map: (function(doc) { emit(doc.integer, doc.integer); }).toString(), reduce: (function(keys, values) { return sum(values); }).toString() }, huge_src_and_results: { map: (function(doc) { if (doc._id === "1") { emit(makebigstring(16), null); } }).toString(), reduce: (function(keys, values) { return makebigstring(16); }).toString() }, lib : { baz : "exports.baz = 'bam';", foo : { foo : "exports.foo = 'bar';", boom : "exports.boom = 'ok';", zoom : "exports.zoom = 'yeah';" } }, commonjs : { map : (function(doc) { emit(null, require('views/lib/foo/boom').boom); }).toString() } }, shows: { simple: (function() { return 'ok'; }).toString(), requirey: (function() { var lib = require('whatever/commonjs/upper'); return lib.testing; }).toString(), circular: (function() { var lib = require('whatever/commonjs/upper'); return JSON.stringify(this); }).toString(), circular_require: (function() { return require('whatever/commonjs/circular_one').name; }).toString(), idtest1: (function() { return require('whatever/idtest1/a/b/d'); }).toString(), idtest2: (function() { return require('whatever/idtest2/a/b/d'); }).toString(), idtest3: (function() { return require('whatever/idtest3/a/b'); }).toString(), idtest4: (function() { return require('whatever/idtest4/a/b'); }).toString(), idtest5: (function() { return require('whatever/idtest5/a'); }).toString() } }; // designDoc var xhr = CouchDB.request( "PUT", "/test_suite_db_a/_design/test", {body: JSON.stringify(designDoc)} ); var resp = JSON.parse(xhr.responseText); TEquals(resp.rev, db.save(designDoc).rev); // test that editing a show fun on the ddoc results in a change in output xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple"); T(xhr.status == 200); TEquals(xhr.responseText, "ok"); designDoc.shows.simple = (function() { return 'ko'; }).toString(); T(db.save(designDoc).ok); xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/simple"); T(xhr.status == 200); TEquals(xhr.responseText, "ko"); xhr = CouchDB.request( "GET", "/test_suite_db_a/_design/test/_show/simple?cache=buster" ); T(xhr.status == 200); TEquals("ok", xhr.responseText, 'query server used wrong ddoc'); // test commonjs require xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/requirey"); T(xhr.status == 200); TEquals("PLANKTONwhatever/commonjs/upperplankton", xhr.responseText); xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/circular"); T(xhr.status == 200); TEquals("javascript", JSON.parse(xhr.responseText).language); // test circular commonjs dependencies xhr = CouchDB.request( "GET", "/test_suite_db/_design/test/_show/circular_require" ); TEquals(200, xhr.status); TEquals("One", xhr.responseText); // Test that changes to the design doc properly invalidate cached modules: // update the designDoc and replace designDoc.whatever.commonjs.circular_one = "exports.name = 'Updated';" T(db.save(designDoc).ok); // request circular_require show function again and check the response has // changed xhr = CouchDB.request( "GET", "/test_suite_db/_design/test/_show/circular_require" ); TEquals(200, xhr.status); TEquals("Updated", xhr.responseText); // test module id values are as expected: xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest1"); TEquals(200, xhr.status); TEquals("whatever/idtest1/a/c/e", xhr.responseText); xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest2"); TEquals(200, xhr.status); TEquals("whatever/idtest2/a/c/e", xhr.responseText); xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest3"); TEquals(200, xhr.status); TEquals("whatever/idtest3/a/c/e", xhr.responseText); xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest4"); TEquals(200, xhr.status); TEquals("whatever/idtest4/a/c/e", xhr.responseText); xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_show/idtest5"); TEquals(200, xhr.status); TEquals("whatever/idtest5/b", xhr.responseText); var prev_view_sig = db.designInfo("_design/test").view_index.signature; var prev_view_size = db.designInfo("_design/test").view_index.disk_size; db.bulkSave(makeDocs(1, numDocs + 1)); T(db.ensureFullCommit().ok); // test that we get correct design doc info back, // and also that GET /db/_design/test/_info // hasn't triggered an update of the views db.view("test/summate", {stale: "ok"}); // make sure view group's open for (var i = 0; i < 2; i++) { var dinfo = db.designInfo("_design/test"); TEquals("test", dinfo.name); var vinfo = dinfo.view_index; TEquals(prev_view_size, vinfo.disk_size, "view group disk size didn't change"); TEquals(false, vinfo.compact_running); TEquals(prev_view_sig, vinfo.signature, 'ddoc sig'); // wait some time (there were issues where an update // of the views had been triggered in the background) var start = new Date().getTime(); while (new Date().getTime() < start + 2000); TEquals(0, db.view("test/all_docs_twice", {stale: "ok"}).total_rows, 'view info'); TEquals(0, db.view("test/single_doc", {stale: "ok"}).total_rows, 'view info'); TEquals(0, db.view("test/summate", {stale: "ok"}).rows.length, 'view info'); T(db.ensureFullCommit().ok); restartServer(); }; db.bulkSave(makeDocs(numDocs + 1, numDocs * 2 + 1)); T(db.ensureFullCommit().ok); // open view group db.view("test/summate", {stale: "ok"}); // wait so the views can get initialized var start = new Date().getTime(); while (new Date().getTime() < start + 2000); // test that POST /db/_view_cleanup // doesn't trigger an update of the views var len1 = db.view("test/all_docs_twice", {stale: "ok"}).total_rows; var len2 = db.view("test/single_doc", {stale: "ok"}).total_rows; var len3 = db.view("test/summate", {stale: "ok"}).rows.length; for (i = 0; i < 2; i++) { T(db.viewCleanup().ok); // wait some time (there were issues where an update // of the views had been triggered in the background) start = new Date().getTime(); while (new Date().getTime() < start + 2000); TEquals(len1, db.view("test/all_docs_twice", {stale: "ok"}).total_rows, 'view cleanup'); TEquals(len2, db.view("test/single_doc", {stale: "ok"}).total_rows, 'view cleanup'); TEquals(len3, db.view("test/summate", {stale: "ok"}).rows.length, 'view cleanup'); T(db.ensureFullCommit().ok); restartServer(); // we'll test whether the view group stays closed // and the views stay uninitialized (they should!) len1 = len2 = len3 = 0; }; // test commonjs in map functions resp = db.view("test/commonjs", {limit:1}); T(resp.rows[0].value == 'ok'); // test that the _all_docs view returns correctly with keys var results = db.allDocs({startkey:"_design", endkey:"_design0"}); T(results.rows.length == 1); for (i = 0; i < 2; i++) { var rows = db.view("test/all_docs_twice").rows; for (var j = 0; j < numDocs; j++) { T(rows[2 * j].key == (j + 1)); T(rows[(2 * j) + 1].key == (j + 1)); }; T(db.view("test/no_docs").total_rows == 0); T(db.view("test/single_doc").total_rows == 1); T(db.ensureFullCommit().ok); restartServer(); }; // test when language not specified, Javascript is implied var designDoc2 = { _id: "_design/test2", // language: "javascript", views: { single_doc: { map: (function(doc) { if (doc._id === "1") { emit(1, null); } }).toString() } } }; T(db.save(designDoc2).ok); T(db.view("test2/single_doc").total_rows == 1); var summate = function(N) { return (N + 1) * (N / 2); }; var result = db.view("test/summate"); T(result.rows[0].value == summate(numDocs * 2)); result = db.view("test/summate", {startkey: 4, endkey: 4}); T(result.rows[0].value == 4); result = db.view("test/summate", {startkey: 4, endkey: 5}); T(result.rows[0].value == 9); result = db.view("test/summate", {startkey: 4, endkey: 6}); T(result.rows[0].value == 15); // test start_key and end_key aliases result = db.view("test/summate", {start_key: 4, end_key: 6}); T(result.rows[0].value == 15); // Verify that a shared index (view def is an exact copy of "summate") // does not confuse the reduce stage result = db.view("test/summate2", {startkey: 4, endkey: 6}); T(result.rows[0].value == 15); for(i = 1; i < (numDocs / 2); i += 30) { result = db.view("test/summate", {startkey: i, endkey: (numDocs - i)}); T(result.rows[0].value == summate(numDocs - i) - summate(i - 1)); } T(db.deleteDoc(designDoc).ok); T(db.open(designDoc._id) == null); T(db.view("test/no_docs") == null); T(db.ensureFullCommit().ok); restartServer(); T(db.open(designDoc._id) == null); T(db.view("test/no_docs") == null); // trigger ddoc cleanup T(db.viewCleanup().ok); }; // enf of testFun run_on_modified_server(server_config, testFun); // cleanup db.deleteDb(); db2.deleteDb(); };