couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jch...@apache.org
Subject svn commit: r727141 - in /couchdb/trunk/share: server/action.js www/script/couch_tests.js
Date Tue, 16 Dec 2008 20:48:00 GMT
Author: jchris
Date: Tue Dec 16 12:48:00 2008
New Revision: 727141

URL: http://svn.apache.org/viewvc?rev=727141&view=rev
Log:
action.js and tests

Added:
    couchdb/trunk/share/server/action.js   (with props)
Modified:
    couchdb/trunk/share/www/script/couch_tests.js

Added: couchdb/trunk/share/server/action.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/server/action.js?rev=727141&view=auto
==============================================================================
--- couchdb/trunk/share/server/action.js (added)
+++ couchdb/trunk/share/server/action.js Tue Dec 16 12:48:00 2008
@@ -0,0 +1,587 @@
+// 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.
+
+// JSON from json2.js
+/*
+    json2.js
+    2008-03-14
+
+    Public Domain
+
+    No warranty expressed or implied. Use at your own risk.
+
+    See http://www.JSON.org/js.html
+
+    This is a reference implementation. You are free to copy, modify, or
+    redistribute.
+
+    Use your own copy. It is extremely unwise to load third party
+    code into your pages.
+*/
+
+var JSON = (function () {
+
+    function f(n) {    // Format integers to have at least two digits.
+        return n < 10 ? '0' + n : n;
+    }
+
+    Date.prototype.toJSON = function () {
+        return this.getUTCFullYear()   + '-' +
+             f(this.getUTCMonth() + 1) + '-' +
+             f(this.getUTCDate())      + 'T' +
+             f(this.getUTCHours())     + ':' +
+             f(this.getUTCMinutes())   + ':' +
+             f(this.getUTCSeconds())   + 'Z';
+    };
+
+
+    var m = {    // table of character substitutions
+        '\b': '\\b',
+        '\t': '\\t',
+        '\n': '\\n',
+        '\f': '\\f',
+        '\r': '\\r',
+        '"' : '\\"',
+        '\\': '\\\\'
+    };
+
+    function stringify(value, whitelist) {
+        var a,          // The array holding the partial texts.
+            i,          // The loop counter.
+            k,          // The member key.
+            l,          // Length.
+            r = /["\\\x00-\x1f\x7f-\x9f]/g,
+            v;          // The member value.
+
+        switch (typeof value) {
+        case 'string':
+
+            return r.test(value) ?
+                '"' + value.replace(r, function (a) {
+                    var c = m[a];
+                    if (c) {
+                        return c;
+                    }
+                    c = a.charCodeAt();
+                    return '\\u00' + Math.floor(c / 16).toString(16) +
+                                               (c % 16).toString(16);
+                }) + '"' :
+                '"' + value + '"';
+
+        case 'number':
+
+            return isFinite(value) ? String(value) : 'null';
+
+        case 'boolean':
+        case 'null':
+            return String(value);
+
+        case 'object':
+
+            if (!value) {
+                return 'null';
+            }
+
+            if (typeof value.toJSON === 'function') {
+                return stringify(value.toJSON());
+            }
+            a = [];
+            if (typeof value.length === 'number' &&
+                    !(value.propertyIsEnumerable('length'))) {
+
+                l = value.length;
+                for (i = 0; i < l; i += 1) {
+                    a.push(stringify(value[i], whitelist) || 'null');
+                }
+
+                return '[' + a.join(',') + ']';
+            }
+            if (whitelist) {
+                l = whitelist.length;
+                for (i = 0; i < l; i += 1) {
+                    k = whitelist[i];
+                    if (typeof k === 'string') {
+                        v = stringify(value[k], whitelist);
+                        if (v) {
+                            a.push(stringify(k) + ':' + v);
+                        }
+                    }
+                }
+            } else {
+
+                for (k in value) {
+                    if (typeof k === 'string') {
+                        v = stringify(value[k], whitelist);
+                        if (v) {
+                            a.push(stringify(k) + ':' + v);
+                        }
+                    }
+                }
+            }
+
+            return '{' + a.join(',') + '}';
+        }
+    }
+
+    return {
+        stringify: stringify,
+        parse: function (text, filter) {
+            var j;
+
+            function walk(k, v) {
+                var i, n;
+                if (v && typeof v === 'object') {
+                    for (i in v) {
+                        if (Object.prototype.hasOwnProperty.apply(v, [i])) {
+                            n = walk(i, v[i]);
+                            if (n !== undefined) {
+                                v[i] = n;
+                            } else {
+                                delete v[i];
+                            }
+                        }
+                    }
+                }
+                return filter(k, v);
+            }
+
+            if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/bfnrtu]/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+                j = eval('(' + text + ')');
+
+                return typeof filter === 'function' ? walk('', j) : j;
+            }
+
+            throw new SyntaxError('parseJSON');
+        }
+    };
+})();
+
+// HTTP object provides access to curl via couchjs.
+
+var HTTP = (function() {
+  function parseCurl(string) {
+    var parts = string.split(/\r\n\r\n/);
+    var body = parts.pop();
+    var header = parts.pop();
+    var headers = header.split(/\n/);
+    
+    var status = /HTTP\/1.\d (\d*)/.exec(header)[1];
+    return {
+      responseText: body,
+      status: parseInt(status),
+      getResponseHeader: function(key) {
+        var keymatcher = new RegExp(RegExp.escape(key), "i");
+        for (var i in headers) {
+          var h = headers[i];
+          if (keymatcher.test(h)) {
+            var value = h.substr(key.length+2);
+            value = value.slice(0, value.length-1);
+            return value;
+          }
+        }
+        return "";
+      }
+    }
+  };
+  return {
+    GET : function(url, body, headers) {
+      var st, urx = url, hx = (headers || null);
+      st = gethttp(urx, hx);
+      return parseCurl(st);
+    },
+    HEAD : function(url, body, headers) {
+      var st, urx = url, hx = (headers || null);
+      st = headhttp(urx, hx);
+      return parseCurl(st);      
+    },
+    DELETE : function(url, body, headers) {
+      var st, urx = url, hx = (headers || null);
+      st = delhttp(urx, hx);
+      return parseCurl(st);
+    },
+    MOVE : function(url, body, headers) {
+      var st, urx = url, hx = (headers || null);
+      st = movehttp(urx, hx);
+      return parseCurl(st);
+    },
+    COPY : function(url, body, headers) {
+      var st, urx = url, hx = (headers || null);
+      st = copyhttp(urx, hx);
+      return parseCurl(st);
+    },
+    POST : function(url, body, headers) {
+      var st, urx = url, bx = (body || ""), hx = (headers || {});
+      hx['Content-Type'] = hx['Content-Type'] || "application/json";
+      st = posthttp(urx, bx, hx);
+      return parseCurl(st);
+    },
+    PUT : function(url, body, headers) {
+      var st, urx = url, bx = (body || ""), hx = (headers || {});
+      hx['Content-Type'] = hx['Content-Type'] || "application/json";
+      st = puthttp(urx, bx, hx);
+      return parseCurl(st);
+    }
+  };
+})();
+
+
+// couch.js
+
+// A simple class to represent a database. Uses XMLHttpRequest to interface with
+// the CouchDB server.
+
+function CouchDB(name) {
+  this.name = name
+  this.uri = "/" + encodeURIComponent(name) + "/";
+  request = CouchDB.request;
+
+  // Creates the database on the server
+  this.createDb = function() {
+    var req = request("PUT", this.uri);
+    var result = JSON.parse(req.responseText);
+    if (req.status != 201)
+      throw result;
+    return result;
+  }
+
+  // Deletes the database on the server
+  this.deleteDb = function() {
+    var req = request("DELETE", this.uri);
+    if (req.status == 404)
+      return false;
+    var result = JSON.parse(req.responseText);
+    if (req.status != 200)
+      throw result;
+    return result;
+  }
+
+  // Save a document to the database
+  this.save = function(doc, options) {
+    var req;
+    if (doc._id == undefined)
+      doc._id = CouchDB.newUuids(1)[0];
+
+    req = request("PUT", this.uri  + encodeURIComponent(doc._id) + encodeOptions(options),
{
+      body: JSON.stringify(doc)
+    });
+    var result = JSON.parse(req.responseText);
+    if (req.status != 201)
+      throw result;
+    doc._rev = result.rev;
+    return result;
+  }
+
+  // Open a document from the database
+  this.open = function(docId, options) {
+    var req = request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options));
+    if (req.status == 404)
+      return null;
+    var result = JSON.parse(req.responseText);
+    if (req.status != 200)
+      throw result;
+    return result;
+  }
+
+  // Deletes a document from the database
+  this.deleteDoc = function(doc) {
+    var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" + doc._rev);
+    var result = JSON.parse(req.responseText);
+    if (req.status != 200)
+      throw result;
+    doc._rev = result.rev; //record rev in input document
+    doc._deleted = true;
+    return result;
+  }
+
+  // Deletes an attachment from a document
+  this.deleteDocAttachment = function(doc, attachment_name) {
+    var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name
+ "?rev=" + doc._rev);
+    var result = JSON.parse(req.responseText);
+    if (req.status != 200)
+      throw result;
+    doc._rev = result.rev; //record rev in input document
+    return result;
+  }
+  
+  this.bulkSave = function(docs, options) {
+    // first prepoulate the UUIDs for new documents
+    var newCount = 0
+    for (var i=0; i<docs.length; i++) {
+      if (docs[i]._id == undefined)
+        newCount++;
+    }
+    var newUuids = CouchDB.newUuids(docs.length);
+    var newCount = 0
+    for (var i=0; i<docs.length; i++) {
+      if (docs[i]._id == undefined)
+        docs[i]._id = newUuids.pop();
+    }
+    var req = request("POST", this.uri + "_bulk_docs" + encodeOptions(options), {
+      body: JSON.stringify({"docs": docs})
+    });
+    var result = JSON.parse(req.responseText);
+    if (req.status != 201)
+      throw result;
+    for (var i = 0; i < docs.length; i++) {
+        docs[i]._rev = result.new_revs[i].rev;
+    }
+    return result;
+  }
+
+  // Applies the map function to the contents of database and returns the results.
+  this.query = function(mapFun, reduceFun, options, keys) {
+    var body = {language: "javascript"};
+    if(keys) {
+      body.keys = keys ;      
+    }
+    if (typeof(mapFun) != "string")
+      mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")";
+    body.map = mapFun;
+    if (reduceFun != null) {
+      if (typeof(reduceFun) != "string")
+        reduceFun = reduceFun.toSource ? reduceFun.toSource() : "(" + reduceFun.toString()
+ ")";
+      body.reduce = reduceFun;
+    }
+    var req = request("POST", this.uri + "_temp_view" + encodeOptions(options), {
+      headers: {"Content-Type": "application/json"},
+      body: JSON.stringify(body)
+    });
+    var result = JSON.parse(req.responseText);
+    if (req.status != 200)
+      throw result;
+    return result;
+  }
+
+  this.view = function(viewname, options, keys) {
+    var req = null ;
+    if(!keys) {
+      req = request("GET", this.uri + "_view/" + viewname + encodeOptions(options));    
 
+    } else {
+      req = request("POST", this.uri + "_view/" + viewname + encodeOptions(options), {
+        headers: {"Content-Type": "application/json"},
+        body: JSON.stringify({keys:keys})
+      });      
+    }
+    if (req.status == 404)
+      return null;
+    var result = JSON.parse(req.responseText);
+    if (req.status != 200)
+      throw result;
+    return result;
+  }
+
+  // gets information about the database
+  this.info = function() {
+    var req = request("GET", this.uri);
+    var result = JSON.parse(req.responseText);
+    if (req.status != 200)
+      throw result;
+    return result;
+  }
+
+  this.allDocs = function(options,keys) {
+    var req = null;
+    if(!keys) {
+      req = request("GET", this.uri + "_all_docs" + encodeOptions(options));      
+    } else {
+      req = request("POST", this.uri + "_all_docs" + encodeOptions(options), {
+        headers: {"Content-Type": "application/json"},
+        body: JSON.stringify({keys:keys})
+      });      
+    }
+    var result = JSON.parse(req.responseText);
+    if (req.status != 200)
+      throw result;
+    return result;
+  }
+
+  this.compact = function() {
+    var req = request("POST", this.uri + "_compact");
+    var result = JSON.parse(req.responseText);
+    if (req.status != 202)
+      throw result;
+    return result;
+  }
+
+  // Convert a options object to an url query string.
+  // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
+  function encodeOptions(options) {
+    var buf = []
+    if (typeof(options) == "object" && options !== null) {
+      for (var name in options) {
+        if (!options.hasOwnProperty(name)) continue;
+        var value = options[name];
+        if (name == "key" || name == "startkey" || name == "endkey") {
+          value = toJSON(value);
+        }
+        buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
+      }
+    }
+    if (!buf.length) {
+      return "";
+    }
+    return "?" + buf.join("&");
+  }
+
+  function toJSON(obj) {
+    return obj !== null ? JSON.stringify(obj) : null;
+  }
+}
+
+CouchDB.allDbs = function() {
+  var req = CouchDB.request("GET", "/_all_dbs");
+  var result = JSON.parse(req.responseText);
+  if (req.status != 200)
+    throw result;
+  return result;
+}
+
+CouchDB.getVersion = function() {
+  var req = CouchDB.request("GET", "/");
+  var result = JSON.parse(req.responseText);
+  if (req.status != 200)
+    throw result;
+  return result.version;
+}
+
+CouchDB.replicate = function(source, target) {
+  var req = CouchDB.request("POST", "/_replicate", {
+    body: JSON.stringify({source: source, target: target})
+  });
+  var result = JSON.parse(req.responseText);
+  if (req.status != 200)
+    throw result;
+  return result;
+}
+
+CouchDB.uuids_cache = [];
+
+CouchDB.newUuids = function(n) {
+  if (CouchDB.uuids_cache.length >= n) {
+    var uuids = CouchDB.uuids_cache.slice(CouchDB.uuids_cache.length - n);
+    if(CouchDB.uuids_cache.length - n == 0) {
+      CouchDB.uuids_cache = [];
+    } else {
+      CouchDB.uuids_cache =
+          CouchDB.uuids_cache.slice(0, CouchDB.uuids_cache.length - n);
+    }
+    return uuids;
+  } else {
+    var req = CouchDB.request("POST", "/_uuids?count=" + (100 + n));
+    var result = JSON.parse(req.responseText);
+    if (req.status != 200)
+      throw result;
+    CouchDB.uuids_cache =
+        CouchDB.uuids_cache.concat(result.uuids.slice(0, 100));
+    return result.uuids.slice(100);
+  }
+}
+
+CouchDB.host = (typeof window == 'undefined' || !window) ? "127.0.0.1:5984" : window;
+
+CouchDB.request = function(method, uri, options) {
+  var full_uri = "http://" + CouchDB.host + uri;
+  options = options || {};
+  var response = HTTP[method](full_uri, options.body, options.headers);
+  return response;
+}
+
+log = function(message) {
+  print(toJSON({log: toJSON(message)}));  
+}
+
+var sandbox = null;
+
+try {
+  // if possible, use evalcx (not always available)
+  sandbox = evalcx('');
+  sandbox.JSON = JSON;
+  sandbox.HTTP = HTTP;
+  sandbox.CouchDB = CouchDB;
+  sandbox.log = log;
+} catch (e) {}
+
+function error(httpcode, type, msg) {
+  return {code: httpcode , json:{error: type, reason: msg}};
+}
+
+function process_request(req) {
+  // Req is an object that contains information about the HTTP
+  // request made to the action server.
+  // Something like: {"verb": "GET", "path": ["foo", "bar"], "query": ..., "post":...}
+  // Here we use "path" to fetch the action, compile it, and run it.
+  if (req.path.length != 2) {
+    return error(404, "action_error", "Invalid path: \"" + req.path.join("/") + "\".");
+  }
+
+  var desname = "_design%2F" + req.path[0];
+  
+  // todo: make this work with other ports and addresses...
+  // maybe couchdb can send config information over to the _external server on boot
+  
+  var desdoc = HTTP.GET("http://127.0.0.1:5984/" + req.info.db_name + "/" + desname);
+
+  if (desdoc.status != 200) {
+    return error(404, "action_error", "Design document '" + desname + "' not found.");
+  }
+
+  var desjson = JSON.parse(desdoc.responseText);
+
+  if (!desjson.actions) {
+    return error(500, "action_error", "No actions found in design doc '" + desname + "'.");
+  }
+
+  if (!desjson.actions[req.path[1]]) {
+    return error(500, "action_error", "No action '" + req.path[1] + "' in design doc '" +
desname + "'");
+  }
+
+  var func = null;
+  try {
+    // todo - use compileFunction from main.js?
+    func = eval(desjson.actions[req.path[1]]);
+  } catch(exception) {
+    return error(500, "action_error", "Failed to compile action: " + JSON.stringify(exception));
+  }
+
+  var ret = null;
+  var db = new CouchDB(req.info.db_name);
+  return func(req, db);
+}
+
+function respond(obj) {
+  print(JSON.stringify(obj));
+};
+
+var req;
+var resp;
+while (req = JSON.parse(readline())) {
+  try {
+    resp = process_request(req);
+  } catch(exception) {
+    if (exception.code) {
+      resp = exception;
+    } else if (exception.error && exception.reason) {
+      resp = error(500, exception.error, exception.reason);      
+    } else {
+      resp = error(500, "action_error", JSON.stringify(exception));      
+    }
+  }
+  
+  try {
+    respond(resp);
+  } catch(e) {
+    respond(error(500, "json_error", "Error encoding JSON response. Exception: "+e.toString()));
+  }
+}
\ No newline at end of file

Propchange: couchdb/trunk/share/server/action.js
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: couchdb/trunk/share/www/script/couch_tests.js
URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/couch_tests.js?rev=727141&r1=727140&r2=727141&view=diff
==============================================================================
--- couchdb/trunk/share/www/script/couch_tests.js [utf-8] (original)
+++ couchdb/trunk/share/www/script/couch_tests.js [utf-8] Tue Dec 16 12:48:00 2008
@@ -1991,7 +1991,199 @@
     xhr = CouchDB.request("GET", "/_config/test/foo");
     T(xhr.responseText == '"bar"');
   },
-  
+
+  actions: function(debug) {
+    var db = new CouchDB("test_suite_db");
+    db.deleteDb();
+    db.createDb();
+    if (debug) debugger;
+
+    var xhr;
+    var numDocs = 10;
+
+    T(db.bulkSave(makeDocs(1, numDocs + 1)).ok);
+    
+    var designDoc = {
+      _id:"_design/no_actions",
+      language: "javascript",
+      views: {
+        single_doc: {map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"}
+      }
+    }
+    T(db.save(designDoc).ok);
+   
+    designDoc = {
+      _id:"_design/errors",
+      language: "javascript",
+      actions: {
+        syntax: "function( bad syntax",
+        except: "function(req) { throw \"Failed to execute\" ; }"
+      }
+    }
+    T(db.save(designDoc).ok);
+   
+    // var testControllerDoc = {
+    //   _id: "_design/test",
+    //   language: "javascript",
+    //   actions: {
+    //     "execution": "function(req) { return {json : req.query.userdata}}",
+    //   }
+    // }
+   
+    designDoc = {
+      _id:"_design/an_action",
+      actions: {
+        "times_two": "function(req) {return {code:200, json: {val: req.query.q * 2}};}",
+        "html" : "function() {return {body:'<p>Lorem ipsum...</p>'}}",
+        "request_object": "function(req) {return {json:req} }",
+        "bad_return": "function(req) {return {foo:\"bar\"} }",
+        "requires_put": 'function(req) {if (req.verb != "PUT") { throw {code:405, body:"Method
Not Allowed, Punk!"}} else { return {body:"thanks for the PUT"}}};'
+      }
+    }
+    T(db.save(designDoc).ok);
+   
+    // Make sure we don't succeed on something that shouldn't
+    xhr = CouchDB.request("GET", "/test_suite_db/_external");
+    T(xhr.status == 404);
+    T(JSON.parse(xhr.responseText).reason == "No server name specified.");
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/bannana");
+    T(xhr.status == 404);
+    T(JSON.parse(xhr.responseText).reason == "No server configured for \"bannana\".");
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action");
+    T(xhr.status == 404);
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/no_actions");
+    T(xhr.status == 404)
+    T(JSON.parse(xhr.responseText).reason == "Invalid path: \"no_actions\".");;
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/no_actions/foo");
+    T(xhr.status == 500);
+    T(/^No actions found/.test(JSON.parse(xhr.responseText).reason));
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/an_action/invalid");
+    T(xhr.status == 500);
+    T(/^No action \'invalid\'/.test(JSON.parse(xhr.responseText).reason));
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/errors/syntax");
+    T(xhr.status == 500);
+    T(/^Failed to compile/.test(JSON.parse(xhr.responseText).reason));
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/errors/except");
+    T(/Failed to execute/.test(JSON.parse(xhr.responseText).reason));
+
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/an_action/times_two");
+    T(xhr.status == 200);
+    T(JSON.parse(xhr.responseText).val == null);
+
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/an_action/bad_return");
+    T(xhr.status == 500);
+    T(/^Invalid data from external server/.test(JSON.parse(xhr.responseText).reason));
+
+
+    // test that we invoke the action server
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/an_action/times_two?q=3");
+    T(xhr.status == 200);
+    T(JSON.parse(xhr.responseText).val == 6);
+    
+    // Test that we can return raw text
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/an_action/html");
+    T(xhr.status == 200);
+    T(xhr.responseText == '<p>Lorem ipsum...</p>');
+    
+    // Test environment
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/an_action/request_object?couchdb=relax");
+    T(xhr.status == 200);
+    var req = JSON.parse(xhr.responseText);
+
+    // Test that actions know their verbs
+    T(req.verb == "GET");
+    // Test that actions know their req
+    T(req.info.db_name == "test_suite_db");
+    T(req.path[0] == "an_action");
+    T(req.path[1] == "request_object");
+    T(req.query.couchdb == "relax");
+
+    xhr = CouchDB.request("POST", "/test_suite_db/_external/action/an_action/request_object?couchdb=relax",{
+      "body" : "some=text",
+      "headers" : {
+        "Content-Type" : "application/x-www-form-urlencoded"
+      }
+    });
+    T(xhr.status == 200);
+    var req = JSON.parse(xhr.responseText);
+    // Test that actions know their verbs
+    T(req.verb == "POST");
+    // Test that actions know their db
+    T(req.info.db_name == "test_suite_db");
+    T(req.path[0] == "an_action");
+    T(req.path[1] == "request_object");
+    // T(req.path[2] == "foo"); // this would be fun
+    T(req.query.couchdb == "relax");
+    
+    // we get raw access to the post body as well as access to the mochiweb-parsed form
+    T(req.body == "some=text");
+    T(req.form.some == "text");
+
+    // we can send error codes back
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/an_action/requires_put");
+    T(xhr.status == 405);
+    T(xhr.responseText == "Method Not Allowed, Punk!");
+    
+    xhr = CouchDB.request("PUT", "/test_suite_db/_external/action/an_action/requires_put");
+    T(xhr.status == 200);
+    T(xhr.responseText == "thanks for the PUT");
+  },
+
+  action_server_requests : function(debug) {
+    var db = new CouchDB("test_suite_db");
+    db.deleteDb();
+    db.createDb();
+    if (debug) debugger;
+
+    // create views for get, post, put and delete
+    var verbControllerDoc = {
+      _id: "_design/verbs",
+      language: "javascript",
+      actions: {
+        "get" : "function(req, db) { var doc = db.open(req.query.docid); return {json:doc}
}",
+        "post" : "function(req, db) { var rs = db.save({'req' : req }); return {json:rs}
}",
+        "put" : "function(req, db) { var rs = db.save({ '_id' : req.query.setid, 'req' :
req.query }); return {json:rs} }",
+        "delete": "function(req, db) { var id = req.query.delid; var doc = db.open(id); var
rd = db.deleteDoc(doc); return {json:rd} }"
+      }
+    }
+    T(db.save(verbControllerDoc).ok);
+
+    // test firing verbs
+
+    // test GET
+    var doc = {foo:"bar"};
+    var result = db.save(doc);
+    var xhr = CouchDB.request("GET", "/test_suite_db/_external/action/verbs/get?docid="+result.id);
+    var resp = JSON.parse(xhr.responseText);
+    T(resp.foo == "bar");   
+
+    // test POST
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/verbs/post?baz=boom");
+    resp = JSON.parse(xhr.responseText);
+    doc = db.open(resp.id);
+    T(doc.req.query.baz == "boom");
+
+    // test PUT
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/verbs/put?setid=mynewdocid&flim=flam");
   
+    doc = db.open("mynewdocid");
+    T(doc.req.flim == "flam");
+
+    // test DELETE
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/verbs/delete?delid=mynewdocid");
   
+    T(db.open("mynewdocid") == null);
+
+    // PUT through on top of an existing id and see the error at the client
+    var created = db.save({
+      _id : "takethisid",
+      key : "value"
+    });
+    T(created.ok);
+    xhr = CouchDB.request("GET", "/test_suite_db/_external/action/verbs/put?setid=takethisid&flim=flam");
   
+    resp = JSON.parse(xhr.responseText);
+    T(resp.error == "conflict");
+    T(resp.reason == "Document update conflict.");
+  },
+
   security_validation : function(debug) {
     // This tests couchdb's security and validation features. This does
     // not test authentication, except to use test authentication code made



Mime
View raw message