blob: 2d74586fe9ffc60836b4272d765589231e1aecb9 [file] [log] [blame]
// 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();
};