| // 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.elixir = true; |
| couchTests.list_views = function(debug) { |
| |
| var db_name = get_random_db_name(); |
| var db = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"}); |
| db.createDb(); |
| if (debug) debugger; |
| |
| var designDoc = { |
| _id:"_design/lists", |
| language: "javascript", |
| views : { |
| basicView : { |
| map : stringFun(function(doc) { |
| emit(doc.integer, doc.string); |
| }) |
| }, |
| withReduce : { |
| map : stringFun(function(doc) { |
| emit(doc.integer, doc.string); |
| }), |
| reduce : stringFun(function(keys, values, rereduce) { |
| if (rereduce) { |
| return sum(values); |
| } else { |
| return values.length; |
| } |
| }) |
| } |
| }, |
| lists: { |
| basicBasic : stringFun(function(head, req) { |
| send("head"); |
| var row; |
| while(row = getRow()) { |
| log("row: "+toJSON(row)); |
| send(row.key); |
| }; |
| return "tail"; |
| }), |
| basicJSON : stringFun(function(head, req) { |
| start({"headers":{"Content-Type" : "application/json"}}); |
| send('{"head":'+toJSON(head)+', '); |
| send('"req":'+toJSON(req)+', '); |
| send('"rows":['); |
| var row, sep = ''; |
| while (row = getRow()) { |
| send(sep + toJSON(row)); |
| sep = ', '; |
| } |
| return "]}"; |
| }), |
| simpleForm: stringFun(function(head, req) { |
| log("simpleForm"); |
| send('<ul>'); |
| var row, row_number = 0, prevKey, firstKey = null; |
| while (row = getRow()) { |
| row_number += 1; |
| if (!firstKey) firstKey = row.key; |
| prevKey = row.key; |
| send('\n<li>Key: '+row.key |
| +' Value: '+row.value |
| +' LineNo: '+row_number+'</li>'); |
| } |
| return '</ul><p>FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'</p>'; |
| }), |
| acceptSwitch: stringFun(function(head, req) { |
| // respondWith takes care of setting the proper headers |
| provides("html", function() { |
| send("HTML <ul>"); |
| |
| var row, num = 0; |
| while (row = getRow()) { |
| num ++; |
| send('\n<li>Key: ' |
| +row.key+' Value: '+row.value |
| +' LineNo: '+num+'</li>'); |
| } |
| |
| // tail |
| return '</ul>'; |
| }); |
| }), |
| qsParams: stringFun(function(head, req) { |
| return toJSON(req.query) + "\n"; |
| }), |
| stopIter: stringFun(function(req) { |
| send("head"); |
| var row, row_number = 0; |
| while(row = getRow()) { |
| if(row_number > 2) break; |
| send(" " + row_number); |
| row_number += 1; |
| }; |
| return " tail"; |
| }), |
| stopIter2: stringFun(function(head, req) { |
| provides("html", function() { |
| send("head"); |
| var row, row_number = 0; |
| while(row = getRow()) { |
| if(row_number > 2) break; |
| send(" " + row_number); |
| row_number += 1; |
| }; |
| return " tail"; |
| }); |
| }), |
| tooManyGetRows : stringFun(function() { |
| send("head"); |
| var row; |
| while(row = getRow()) { |
| send(row.key); |
| }; |
| getRow(); |
| getRow(); |
| getRow(); |
| row = getRow(); |
| return "after row: "+toJSON(row); |
| }), |
| emptyList: stringFun(function() { |
| return " "; |
| }), |
| rowError : stringFun(function(head, req) { |
| send("head"); |
| var row = getRow(); |
| send(fooBarBam); // intentional error |
| return "tail"; |
| }), |
| docReference : stringFun(function(head, req) { |
| send("head"); |
| var row = getRow(); |
| send(row.doc.integer); |
| return "tail"; |
| }), |
| secObj: stringFun(function(head, req) { |
| return toJSON(req.secObj); |
| }), |
| setHeaderAfterGotRow: stringFun(function(head, req) { |
| getRow(); |
| start({ |
| code: 400, |
| headers: { |
| "X-My-Header": "MyHeader" |
| } |
| }); |
| send("bad request"); |
| }), |
| allDocs: stringFun(function(head, req){ |
| start({'headers': {'Content-Type': 'application/json'}}); |
| var resp = head; |
| var rows = []; |
| while(row=getRow()){ |
| rows.push(row); |
| } |
| resp.rows = rows; |
| return toJSON(resp); |
| }) |
| } |
| }; |
| var viewOnlyDesignDoc = { |
| _id:"_design/views", |
| language: "javascript", |
| views : { |
| basicView : { |
| map : stringFun(function(doc) { |
| emit(-doc.integer, doc.string); |
| }) |
| } |
| } |
| }; |
| var erlListDoc = { |
| _id: "_design/erlang", |
| language: "erlang", |
| lists: { |
| simple: |
| 'fun(Head, {Req}) -> ' + |
| ' Send(<<"[">>), ' + |
| ' Fun = fun({Row}, Sep) -> ' + |
| ' Val = couch_util:get_value(<<"key">>, Row, 23), ' + |
| ' Send(list_to_binary(Sep ++ integer_to_list(Val))), ' + |
| ' {ok, ","} ' + |
| ' end, ' + |
| ' {ok, _} = FoldRows(Fun, ""), ' + |
| ' Send(<<"]">>) ' + |
| 'end.' |
| } |
| }; |
| |
| T(db.save(designDoc).ok); |
| |
| var docs = makeDocs(0, 10); |
| db.bulkSave(docs); |
| |
| var view = db.view('lists/basicView'); |
| T(view.total_rows == 10); |
| |
| // standard get |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/basicBasic/basicView"); |
| T(xhr.status == 200, "standard get should be 200"); |
| T(/head0123456789tail/.test(xhr.responseText)); |
| |
| // standard options - works though it does not make lots of sense |
| var xhr = CouchDB.request("OPTIONS", "/" + db_name + "/_design/lists/_list/basicBasic/basicView"); |
| T(xhr.status == 200, "standard get should be 200"); |
| T(/head0123456789tail/.test(xhr.responseText)); |
| |
| // TODO: test that etags are available - actually they're not (yet): https://issues.apache.org/jira/browse/COUCHDB-2859 |
| //var etag = xhr.getResponseHeader("etag"); |
| //xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/basicBasic/basicView", { |
| // headers: {"if-none-match": etag} |
| //}); |
| //T(xhr.status == 304); |
| |
| // confirm ETag changes with different POST bodies |
| // (not yet - see above) |
| //xhr = CouchDB.request("POST", "/" + db_name + "/_design/lists/_list/basicBasic/basicView", |
| // {body: JSON.stringify({keys:[1]})} |
| //); |
| //var etag1 = xhr.getResponseHeader("etag"); |
| //xhr = CouchDB.request("POST", "/" + db_name + "/_design/lists/_list/basicBasic/basicView", |
| // {body: JSON.stringify({keys:[2]})} |
| //); |
| //var etag2 = xhr.getResponseHeader("etag"); |
| //T(etag1 != etag2, "POST to map _list generates key-depdendent ETags"); |
| |
| // test the richness of the arguments |
| xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/basicJSON/basicView?update_seq=true"); |
| T(xhr.status == 200, "standard get should be 200"); |
| var resp = JSON.parse(xhr.responseText); |
| TEquals(10, resp.head.total_rows); |
| TEquals(0, resp.head.offset); |
| // we don't have a (meaningful) update seq in a clustered env |
| //TEquals(11, resp.head.update_seq); |
| |
| T(resp.rows.length == 10); |
| TEquals(resp.rows[0], {"id": "0","key": 0,"value": "0"}); |
| |
| TEquals(resp.req.info.db_name, "" + db_name + ""); |
| TEquals(resp.req.method, "GET"); |
| TEquals(resp.req.path, [ |
| "" + db_name + "", |
| "_design", |
| "lists", |
| "_list", |
| "basicJSON", |
| "basicView" |
| ]); |
| T(resp.req.headers.Accept); |
| T(resp.req.headers.Host); |
| T(resp.req.headers["User-Agent"]); |
| T(resp.req.cookie); |
| TEquals("/" + db_name + "/_design/lists/_list/basicJSON/basicView?update_seq=true", |
| resp.req.raw_path, "should include raw path"); |
| |
| // get with query params |
| xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/simpleForm/basicView?startkey=3&endkey=8"); |
| T(xhr.status == 200, "with query params"); |
| T(!(/Key: 1/.test(xhr.responseText))); |
| T(/FirstKey: 3/.test(xhr.responseText)); |
| T(/LastKey: 8/.test(xhr.responseText)); |
| |
| // with 0 rows |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/simpleForm/basicView?startkey=30"); |
| T(xhr.status == 200, "0 rows"); |
| T(/<\/ul>/.test(xhr.responseText)); |
| |
| //too many Get Rows |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/tooManyGetRows/basicView"); |
| T(xhr.status == 200, "tooManyGetRows"); |
| T(/9after row: null/.test(xhr.responseText)); |
| |
| |
| // reduce with 0 rows |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/simpleForm/withReduce?startkey=30"); |
| T(xhr.status == 200, "reduce 0 rows"); |
| T(/LastKey: undefined/.test(xhr.responseText)); |
| |
| // when there is a reduce present, but not used |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/simpleForm/withReduce?reduce=false"); |
| T(xhr.status == 200, "reduce false"); |
| T(/Key: 1/.test(xhr.responseText)); |
| |
| |
| // when there is a reduce present, and used |
| xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/simpleForm/withReduce?group=true"); |
| T(xhr.status == 200, "group reduce"); |
| T(/Key: 1/.test(xhr.responseText)); |
| |
| // there should be etags on reduce as well |
| // (see above 4 etags) |
| //var etag = xhr.getResponseHeader("etag"); |
| //T(etag, "Etags should be served with reduce lists"); |
| //xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/simpleForm/withReduce?group=true", { |
| // headers: {"if-none-match": etag} |
| //}); |
| //T(xhr.status == 304); |
| |
| // confirm ETag changes with different POST bodies |
| // (see above) |
| //xhr = CouchDB.request("POST", "/" + db_name + "/_design/lists/_list/simpleForm/withReduce?group=true", |
| // {body: JSON.stringify({keys:[1]})} |
| //); |
| //var etag1 = xhr.getResponseHeader("etag"); |
| //xhr = CouchDB.request("POST", "/" + db_name + "/_design/lists/_list/simpleForm/withReduce?group=true", |
| // {body: JSON.stringify({keys:[2]})} |
| //); |
| //var etag2 = xhr.getResponseHeader("etag"); |
| //T(etag1 != etag2, "POST to reduce _list generates key-depdendent ETags"); |
| |
| // verify the etags expire correctly |
| var docs = makeDocs(11, 12); |
| db.bulkSave(docs); |
| |
| xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/simpleForm/withReduce?group=true", { |
| // will always be 200 as etags don't make sense (see above) |
| //headers: {"if-none-match": etag} |
| }); |
| T(xhr.status == 200, "reduce etag"); |
| |
| // empty list |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/emptyList/basicView"); |
| T(xhr.responseText.match(/^ $/)); |
| xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/emptyList/withReduce?group=true"); |
| T(xhr.responseText.match(/^ $/)); |
| |
| // multi-key fetch |
| var xhr = CouchDB.request("POST", "/" + db_name + "/_design/lists/_list/simpleForm/basicView", { |
| body: '{"keys":[2,4,5,7]}' |
| }); |
| T(xhr.status == 200, "multi key"); |
| T(!(/Key: 1 /.test(xhr.responseText))); |
| T(/Key: 2/.test(xhr.responseText)); |
| T(/FirstKey: 2/.test(xhr.responseText)); |
| T(/LastKey: 7/.test(xhr.responseText)); |
| |
| // multi-key fetch with GET |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/simpleForm/basicView" + |
| "?keys=[2,4,5,7]"); |
| |
| T(xhr.status == 200, "multi key"); |
| T(!(/Key: 1 /.test(xhr.responseText))); |
| T(/Key: 2/.test(xhr.responseText)); |
| T(/FirstKey: 2/.test(xhr.responseText)); |
| T(/LastKey: 7/.test(xhr.responseText)); |
| |
| // no multi-key fetch allowed when group=false |
| xhr = CouchDB.request("POST", "/" + db_name + "/_design/lists/_list/simpleForm/withReduce?group=false", { |
| body: '{"keys":[2,4,5,7]}' |
| }); |
| T(xhr.status == 400); |
| T(/query_parse_error/.test(xhr.responseText)); |
| |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/rowError/basicView"); |
| T(/ReferenceError/.test(xhr.responseText)); |
| |
| |
| // with include_docs and a reference to the doc. |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/docReference/basicView?include_docs=true"); |
| T(xhr.responseText.match(/head0tail/)); |
| |
| // now with extra qs params |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/qsParams/basicView?foo=blam"); |
| T(xhr.responseText.match(/blam/)); |
| |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/stopIter/basicView"); |
| // T(xhr.getResponseHeader("Content-Type") == "text/plain"); |
| T(xhr.responseText.match(/^head 0 1 2 tail$/) && "basic stop"); |
| |
| xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/stopIter2/basicView", { |
| headers : { |
| "Accept" : "text/html" |
| } |
| }); |
| T(xhr.responseText.match(/^head 0 1 2 tail$/) && "stop 2"); |
| |
| // aborting iteration with reduce |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/stopIter/withReduce?group=true"); |
| T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop"); |
| xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/stopIter2/withReduce?group=true", { |
| headers : { |
| "Accept" : "text/html" |
| } |
| }); |
| T(xhr.responseText.match(/^head 0 1 2 tail$/) && "reduce stop 2"); |
| |
| // with accept headers for HTML |
| xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/acceptSwitch/basicView", { |
| headers: { |
| "Accept": 'text/html' |
| } |
| }); |
| T(xhr.getResponseHeader("Content-Type") == "text/html; charset=utf-8"); |
| T(xhr.responseText.match(/HTML/)); |
| T(xhr.responseText.match(/Value/)); |
| |
| // Test we can run lists and views from separate docs. |
| T(db.save(viewOnlyDesignDoc).ok); |
| var url = "/" + db_name + "/_design/lists/_list/simpleForm/views/basicView" + |
| "?startkey=-3"; |
| xhr = CouchDB.request("GET", url); |
| T(xhr.status == 200, "multiple design docs."); |
| T(!(/Key: -4/.test(xhr.responseText))); |
| T(/FirstKey: -3/.test(xhr.responseText)); |
| T(/LastKey: 0/.test(xhr.responseText)); |
| |
| // Test we do multi-key requests on lists and views in separate docs. |
| var url = "/" + db_name + "/_design/lists/_list/simpleForm/views/basicView"; |
| xhr = CouchDB.request("POST", url, { |
| body: '{"keys":[-2,-4,-5,-7]}' |
| }); |
| |
| T(xhr.status == 200, "multi key separate docs"); |
| T(!(/Key: -3/.test(xhr.responseText))); |
| T(/Key: -7/.test(xhr.responseText)); |
| T(/FirstKey: -2/.test(xhr.responseText)); |
| T(/LastKey: -7/.test(xhr.responseText)); |
| |
| // Test if secObj is available |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/secObj/basicView"); |
| T(xhr.status == 200, "standard get should be 200"); |
| var resp = JSON.parse(xhr.responseText); |
| T(typeof(resp) == "object"); |
| |
| var erlViewTest = function() { |
| T(db.save(erlListDoc).ok); |
| var url = "/" + db_name + "/_design/erlang/_list/simple/views/basicView" + |
| "?startkey=-3"; |
| xhr = CouchDB.request("GET", url); |
| T(xhr.status == 200, "multiple languages in design docs."); |
| var list = JSON.parse(xhr.responseText); |
| T(list.length == 4); |
| for(var i = 0; i < list.length; i++) |
| { |
| T(list[i] + 3 == i); |
| } |
| }; |
| |
| // make _config available 4 tests or leave commented out |
| //run_on_modified_server([{ |
| // section: "native_query_servers", |
| // key: "erlang", |
| // value: "{couch_native_process, start_link, []}" |
| //}], erlViewTest); |
| |
| // COUCHDB-1113 |
| var ddoc = { |
| _id: "_design/test", |
| views: { |
| me: { |
| map: (function(doc) { emit(null,null)}).toString() |
| } |
| }, |
| lists: { |
| you: (function(head, req) { |
| var row; |
| while(row = getRow()) { |
| send(row); |
| } |
| }).toString() |
| } |
| }; |
| db.save(ddoc); |
| |
| var resp = CouchDB.request("GET", "/" + db.name + "/_design/test/_list/you/me", { |
| headers: { |
| "Content-Type": "application/x-www-form-urlencoded" |
| } |
| }); |
| TEquals(200, resp.status, "should return a 200 response"); |
| |
| // TEST HTTP header response set after getRow() called in _list function. |
| var xhr = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/setHeaderAfterGotRow/basicView"); |
| T(xhr.status == 400); |
| T(xhr.getResponseHeader("X-My-Header") == "MyHeader"); |
| T(xhr.responseText.match(/^bad request$/)); |
| |
| // test handling _all_docs by _list functions. the result should be equal |
| var xhr_lAllDocs = CouchDB.request("GET", "/" + db_name + "/_design/lists/_list/allDocs/_all_docs"); |
| T(xhr_lAllDocs.status == 200, "standard get should be 200"); |
| var xhr_allDocs = CouchDB.request("GET", "/" + db_name + "/_all_docs"); |
| var allDocs = JSON.parse(xhr_allDocs.responseText); |
| var lAllDocs = JSON.parse(xhr_lAllDocs.responseText); |
| TEquals(allDocs.total_rows, lAllDocs.total_rows, "total_rows mismatch"); |
| TEquals(allDocs.offset, lAllDocs.offset, "offset mismatch"); |
| TEquals(allDocs.rows.length, lAllDocs.rows.length, "amount of rows mismatch"); |
| TEquals(allDocs.rows, lAllDocs.rows, "rows mismatch"); |
| |
| // cleanup |
| db.deleteDb(); |
| }; |