launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #11720
[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