← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~lifeless/js-oopsd/post into lp:js-oopsd

 

Robert Collins has proposed merging lp:~lifeless/js-oopsd/post into lp:js-oopsd.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~lifeless/js-oopsd/post/+merge/123439

Add support for testr, and support for POSTed requests (which are
generally preferrable, as using the GET hack is fugly, though needed
sometimes).
-- 
https://code.launchpad.net/~lifeless/js-oopsd/post/+merge/123439
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~lifeless/js-oopsd/post into lp:js-oopsd.
=== added file '.testr.conf'
--- .testr.conf	1970-01-01 00:00:00 +0000
+++ .testr.conf	2012-09-09 04:11:19 +0000
@@ -0,0 +1,4 @@
+[DEFAULT]
+test_command=PYTHONPATH=. bin/py -m subunit.run $LISTOPT $IDOPTION discover .
+test_id_option=--load-list $IDFILE
+test_list_option=--list

=== modified file 'NEWS'
--- NEWS	2011-09-23 12:11:32 +0000
+++ NEWS	2012-09-09 04:11:19 +0000
@@ -1,8 +1,9 @@
 js-oopsd NEWS
 +++++++++++++
 
-Changes and improvements to oops-twisted, grouped by release.
+Changes and improvements to js-oopsd, grouped by release.
 
 NEXT
 ----
 
+* GET and POST support added.

=== modified file 'README'
--- README	2012-08-10 05:54:41 +0000
+++ README	2012-09-09 04:11:19 +0000
@@ -51,8 +51,12 @@
 The service can then be run with 'python -m js_oopd.main' (if you used buildout
 use 'bin/py -m js_oopsd.main')
 
-Anything that can make GET requests with a query string that is urlencoded JSON
-can report errors via this service.
+The service exposes two generic HTTP mechanisms for reporting errors:
+
+* Make GET requests to / with a query string that is urlencoded JSON following
+  the same schema that python-oops uses.
+
+* Make a POST to / with the body of the POST the JSON error report.
 
 To report errors from a web browser, you can use the bundled error reporting
 routine from js/oops.js. You can copy this file into your own project - it is
@@ -78,3 +82,8 @@
 For instance::
 
   $ bin/py -m testtools.run js_oopsd.tests.test_suite
+
+Alternatively, if you have testrepository, you can use that::
+
+  $ testr init
+  $ testr run

=== modified file 'buildout.cfg'
--- buildout.cfg	2011-09-23 12:11:32 +0000
+++ buildout.cfg	2012-09-09 04:11:19 +0000
@@ -34,5 +34,5 @@
 eggs = js_oopsd [test]
 include-site-packages = true
 allowed-eggs-from-site-packages = 
-    subunit
+    python-subunit subunit
 interpreter = py

=== modified file 'js_oopsd/__init__.py'
--- js_oopsd/__init__.py	2011-09-23 12:11:32 +0000
+++ js_oopsd/__init__.py	2012-09-09 04:11:19 +0000
@@ -39,19 +39,29 @@
 ============
 
 Either run setup.py in an environment with all the dependencies available, or
-add the working directory to your PYTHONPATH.  Alternatively run ./bootstrap.py
-to create bin/buildout, then bin/buildout to create a bin/py and finally bin/py
--m gpverify.main.
+add the working directory to your PYTHONPATH.  Alternatively run::
+ $ make bin/buildout
+ $ bin/buildout
 
-The service can then be run with 'python -m js_oopd.smain' (if you used buildout
+The service can then be run with 'python -m js_oopd.main' (if you used buildout
 use 'bin/py -m js_oopsd.main')
 
-The Javascript to install the error handler in a browser is in js/oops.js, and
-should be hooked into onLoad thusly::
-
- ... <fill this out>
-
-JS-OOPSd only supports YUI so far.
+The service exposes two generic HTTP mechanisms for reporting errors:
+
+* Make GET requests to / with a query string that is urlencoded JSON following
+  the same schema that python-oops uses.
+
+* Make a POST to / with the body of the POST the JSON error report.
+
+To report errors from a web browser, you can use the bundled error reporting
+routine from js/oops.js. You can copy this file into your own project - it is
+in the public domain. To configure oops reporting in your page::
+
+<script type="text/javascript" src="https://<yourserver>/jsoops.js"></script>
+<script type="text/javascript">
+    jsoops.setEndPoint('https://<yourjsoopsdurl>');
+    jsoops.setReporter('<yourreporter>');
+</script>
 
 Development
 ===========
@@ -67,6 +77,11 @@
 For instance::
 
   $ bin/py -m testtools.run js_oopsd.tests.test_suite
+
+Alternatively, if you have testrepository, you can use that::
+
+  $ testr init
+  $ testr run
 """
 
 

=== modified file 'js_oopsd/main.py'
--- js_oopsd/main.py	2012-08-10 04:38:03 +0000
+++ js_oopsd/main.py	2012-09-09 04:11:19 +0000
@@ -41,12 +41,31 @@
             ]:
             start_response('404 Not Found', [])
             return
-        query = environ.get('QUERY_STRING', '')
-        query = unquote(query)
-        if not query:
-            start_response('404 Not Found', [])
-            return
-        report_extra = json.loads(query)
+        method = environ['REQUEST_METHOD']
+        if method == 'POST':
+            length = environ.get('CONTENT_LENGTH', None)
+            if length:
+                json_content = environ['wsgi.input'].read(int(length))
+            else:
+                # Transfer-coded, wsgi container has to handle length
+                # calculations.
+                json_content = environ['wsgi.input'].read()
+        elif method == 'GET':
+            query = environ.get('QUERY_STRING', '')
+            json_content = unquote(query)
+        else:
+            # Not supported
+            start_response('405 Method Not Allowed', [('Allow', 'GET, POST')])
+            return
+        if not json_content:
+            start_response('400 Empty OOPS report', [])
+            return
+        try:
+            report_extra = json.loads(json_content)
+        except ValueError:
+            # Not JSON
+            start_response('400 Bad report - invalid JSON', [])
+            return
         if type(report_extra) is not dict:
             start_response('404 Not Found', [])
             return

=== modified file 'js_oopsd/tests/__init__.py'
--- js_oopsd/tests/__init__.py	2011-09-23 12:11:32 +0000
+++ js_oopsd/tests/__init__.py	2012-09-09 04:11:19 +0000
@@ -21,7 +21,7 @@
 
 def test_suite():
     test_mod_names = [
-        # 'main',
+        'main',
         ]
     return TestLoader().loadTestsFromNames(
         ['js_oopsd.tests.test_' + name for name in test_mod_names])

=== added file 'js_oopsd/tests/test_main.py'
--- js_oopsd/tests/test_main.py	1970-01-01 00:00:00 +0000
+++ js_oopsd/tests/test_main.py	2012-09-09 04:11:19 +0000
@@ -0,0 +1,101 @@
+# Copyright (c) 2012, Canonical Ltd
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+from StringIO import StringIO
+
+from oops.config import Config
+from testtools import TestCase
+from testtools.matchers import Equals
+
+from js_oopsd.main import make_app
+
+def capture():
+    results = []
+    def callback(oops):
+        results.append(oops)
+        return [str(len(results))]
+    return results, callback
+
+
+def test_app():
+    config = Config()
+    app = make_app(config)
+    events, publisher = capture()
+    config.publisher = publisher
+    # discard on-create hooks for deterministic test data.
+    config.on_create = []
+    def start_response(status, headers):
+        events.append((status, headers))
+        return events.append
+    return app, events, start_response
+
+
+
+class TestApp(TestCase):
+
+    def test_not_get_post(self):
+        app, events, start_response = test_app()
+        environ = {
+            'PATH_INFO': '/',
+            'REQUEST_METHOD': 'FOO',
+            }
+        output = list(app(environ, start_response))
+        self.assertThat(output, Equals([]))
+        self.assertThat(events,
+            Equals([('405 Method Not Allowed', [('Allow', 'GET, POST')])]))
+    
+    def test_post_empty(self):
+        # Empty generates a error response.
+        app, events, start_response = test_app()
+        environ = {
+            'PATH_INFO': '/',
+            'REQUEST_METHOD': 'POST',
+            'CONTENT_LENGTH': '0',
+            'wsgi.input': StringIO(),
+            }
+        output = list(app(environ, start_response))
+        self.assertThat(output, Equals([]))
+        self.assertThat(events,
+            Equals([('400 Empty OOPS report', [])]))
+
+    def test_post_nonjson(self):
+        # Posting random crap is caught.
+        app, events, start_response = test_app()
+        environ = {
+            'PATH_INFO': '/',
+            'REQUEST_METHOD': 'POST',
+            'CONTENT_LENGTH': '12',
+            'wsgi.input': StringIO('[random junk'),
+            }
+        output = list(app(environ, start_response))
+        self.assertThat(output, Equals([]))
+        self.assertThat(events,
+            Equals([('400 Bad report - invalid JSON', [])]))
+
+    def test_post_json(self):
+        app, events, start_response = test_app()
+        environ = {
+            'PATH_INFO': '/',
+            'REQUEST_METHOD': 'POST',
+            'CONTENT_LENGTH': '20',
+            'wsgi.input': StringIO('{"reporter": "fred"}'),
+            }
+        output = list(app(environ, start_response))
+        self.assertThat(output, Equals(['']))
+        self.assertThat(events,
+            Equals([{'id': '1', u'reporter': u'fred'},
+                    ('200 OK', [('X-OOPS-ID', '1')])]))
+

=== modified file 'setup.py'
--- setup.py	2012-08-09 23:57:35 +0000
+++ setup.py	2012-09-09 04:11:19 +0000
@@ -48,6 +48,7 @@
       extras_require = dict(
           test=[
               'testtools',
+              'python-subunit',
               ]
           ),
       )

=== modified file 'versions.cfg'
--- versions.cfg	2012-08-10 03:57:06 +0000
+++ versions.cfg	2012-09-09 04:11:19 +0000
@@ -20,6 +20,7 @@
 oops-wsgi = 0.0.10
 paste = 1.7.2
 pymongo = 2.1.1
+python-subunit = 0.0.8
 pytz = 2012c
 setuptools = 0.6c11
 simplejson = 2.2.1