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

Proposed by Robert Collins
Status: Merged
Merged at revision: 12
Proposed branch: lp:~lifeless/js-oopsd/post
Merge into: lp:js-oopsd
Diff against target: 303 lines (+172/-21)
10 files modified
.testr.conf (+4/-0)
NEWS (+2/-1)
README (+11/-2)
buildout.cfg (+1/-1)
js_oopsd/__init__.py (+25/-10)
js_oopsd/main.py (+25/-6)
js_oopsd/tests/__init__.py (+1/-1)
js_oopsd/tests/test_main.py (+101/-0)
setup.py (+1/-0)
versions.cfg (+1/-0)
To merge this branch: bzr merge lp:~lifeless/js-oopsd/post
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code Approve
Review via email: mp+123439@code.launchpad.net

Description of the change

Add support for testr, and support for POSTed requests (which are
generally preferrable, as using the GET hack is fugly, though needed
sometimes).

To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) wrote :

My only niggle is you repeat the two methods description verbatim in README and buildout.cfg.

review: Approve (code)
Revision history for this message
Robert Collins (lifeless) wrote :

ITYM README and __init__.py. That said, yes the docs are duplicated, we need to do some legwork to come up with / reuse some existing single-source-of-truth for the top level narrative that is also embedded into README, and probably, if we ever do them, rtfd docs.

lp:~lifeless/js-oopsd/post updated
12. By Robert Collins

* GET and POST support added.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.testr.conf'
2--- .testr.conf 1970-01-01 00:00:00 +0000
3+++ .testr.conf 2012-09-09 04:11:19 +0000
4@@ -0,0 +1,4 @@
5+[DEFAULT]
6+test_command=PYTHONPATH=. bin/py -m subunit.run $LISTOPT $IDOPTION discover .
7+test_id_option=--load-list $IDFILE
8+test_list_option=--list
9
10=== modified file 'NEWS'
11--- NEWS 2011-09-23 12:11:32 +0000
12+++ NEWS 2012-09-09 04:11:19 +0000
13@@ -1,8 +1,9 @@
14 js-oopsd NEWS
15 +++++++++++++
16
17-Changes and improvements to oops-twisted, grouped by release.
18+Changes and improvements to js-oopsd, grouped by release.
19
20 NEXT
21 ----
22
23+* GET and POST support added.
24
25=== modified file 'README'
26--- README 2012-08-10 05:54:41 +0000
27+++ README 2012-09-09 04:11:19 +0000
28@@ -51,8 +51,12 @@
29 The service can then be run with 'python -m js_oopd.main' (if you used buildout
30 use 'bin/py -m js_oopsd.main')
31
32-Anything that can make GET requests with a query string that is urlencoded JSON
33-can report errors via this service.
34+The service exposes two generic HTTP mechanisms for reporting errors:
35+
36+* Make GET requests to / with a query string that is urlencoded JSON following
37+ the same schema that python-oops uses.
38+
39+* Make a POST to / with the body of the POST the JSON error report.
40
41 To report errors from a web browser, you can use the bundled error reporting
42 routine from js/oops.js. You can copy this file into your own project - it is
43@@ -78,3 +82,8 @@
44 For instance::
45
46 $ bin/py -m testtools.run js_oopsd.tests.test_suite
47+
48+Alternatively, if you have testrepository, you can use that::
49+
50+ $ testr init
51+ $ testr run
52
53=== modified file 'buildout.cfg'
54--- buildout.cfg 2011-09-23 12:11:32 +0000
55+++ buildout.cfg 2012-09-09 04:11:19 +0000
56@@ -34,5 +34,5 @@
57 eggs = js_oopsd [test]
58 include-site-packages = true
59 allowed-eggs-from-site-packages =
60- subunit
61+ python-subunit subunit
62 interpreter = py
63
64=== modified file 'js_oopsd/__init__.py'
65--- js_oopsd/__init__.py 2011-09-23 12:11:32 +0000
66+++ js_oopsd/__init__.py 2012-09-09 04:11:19 +0000
67@@ -39,19 +39,29 @@
68 ============
69
70 Either run setup.py in an environment with all the dependencies available, or
71-add the working directory to your PYTHONPATH. Alternatively run ./bootstrap.py
72-to create bin/buildout, then bin/buildout to create a bin/py and finally bin/py
73--m gpverify.main.
74+add the working directory to your PYTHONPATH. Alternatively run::
75+ $ make bin/buildout
76+ $ bin/buildout
77
78-The service can then be run with 'python -m js_oopd.smain' (if you used buildout
79+The service can then be run with 'python -m js_oopd.main' (if you used buildout
80 use 'bin/py -m js_oopsd.main')
81
82-The Javascript to install the error handler in a browser is in js/oops.js, and
83-should be hooked into onLoad thusly::
84-
85- ... <fill this out>
86-
87-JS-OOPSd only supports YUI so far.
88+The service exposes two generic HTTP mechanisms for reporting errors:
89+
90+* Make GET requests to / with a query string that is urlencoded JSON following
91+ the same schema that python-oops uses.
92+
93+* Make a POST to / with the body of the POST the JSON error report.
94+
95+To report errors from a web browser, you can use the bundled error reporting
96+routine from js/oops.js. You can copy this file into your own project - it is
97+in the public domain. To configure oops reporting in your page::
98+
99+<script type="text/javascript" src="https://<yourserver>/jsoops.js"></script>
100+<script type="text/javascript">
101+ jsoops.setEndPoint('https://<yourjsoopsdurl>');
102+ jsoops.setReporter('<yourreporter>');
103+</script>
104
105 Development
106 ===========
107@@ -67,6 +77,11 @@
108 For instance::
109
110 $ bin/py -m testtools.run js_oopsd.tests.test_suite
111+
112+Alternatively, if you have testrepository, you can use that::
113+
114+ $ testr init
115+ $ testr run
116 """
117
118
119
120=== modified file 'js_oopsd/main.py'
121--- js_oopsd/main.py 2012-08-10 04:38:03 +0000
122+++ js_oopsd/main.py 2012-09-09 04:11:19 +0000
123@@ -41,12 +41,31 @@
124 ]:
125 start_response('404 Not Found', [])
126 return
127- query = environ.get('QUERY_STRING', '')
128- query = unquote(query)
129- if not query:
130- start_response('404 Not Found', [])
131- return
132- report_extra = json.loads(query)
133+ method = environ['REQUEST_METHOD']
134+ if method == 'POST':
135+ length = environ.get('CONTENT_LENGTH', None)
136+ if length:
137+ json_content = environ['wsgi.input'].read(int(length))
138+ else:
139+ # Transfer-coded, wsgi container has to handle length
140+ # calculations.
141+ json_content = environ['wsgi.input'].read()
142+ elif method == 'GET':
143+ query = environ.get('QUERY_STRING', '')
144+ json_content = unquote(query)
145+ else:
146+ # Not supported
147+ start_response('405 Method Not Allowed', [('Allow', 'GET, POST')])
148+ return
149+ if not json_content:
150+ start_response('400 Empty OOPS report', [])
151+ return
152+ try:
153+ report_extra = json.loads(json_content)
154+ except ValueError:
155+ # Not JSON
156+ start_response('400 Bad report - invalid JSON', [])
157+ return
158 if type(report_extra) is not dict:
159 start_response('404 Not Found', [])
160 return
161
162=== modified file 'js_oopsd/tests/__init__.py'
163--- js_oopsd/tests/__init__.py 2011-09-23 12:11:32 +0000
164+++ js_oopsd/tests/__init__.py 2012-09-09 04:11:19 +0000
165@@ -21,7 +21,7 @@
166
167 def test_suite():
168 test_mod_names = [
169- # 'main',
170+ 'main',
171 ]
172 return TestLoader().loadTestsFromNames(
173 ['js_oopsd.tests.test_' + name for name in test_mod_names])
174
175=== added file 'js_oopsd/tests/test_main.py'
176--- js_oopsd/tests/test_main.py 1970-01-01 00:00:00 +0000
177+++ js_oopsd/tests/test_main.py 2012-09-09 04:11:19 +0000
178@@ -0,0 +1,101 @@
179+# Copyright (c) 2012, Canonical Ltd
180+#
181+# This program is free software: you can redistribute it and/or modify
182+# it under the terms of the GNU Affero General Public License as published by
183+# the Free Software Foundation, either version 3 of the License, or
184+# (at your option) any later version.
185+#
186+# This program is distributed in the hope that it will be useful,
187+# but WITHOUT ANY WARRANTY; without even the implied warranty of
188+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
189+# GNU Affero General Public License for more details.
190+#
191+# You should have received a copy of the GNU Affero General Public License
192+# along with this program. If not, see <http://www.gnu.org/licenses/>.
193+# GNU Affero General Public License version 3 (see the file LICENSE).
194+
195+from StringIO import StringIO
196+
197+from oops.config import Config
198+from testtools import TestCase
199+from testtools.matchers import Equals
200+
201+from js_oopsd.main import make_app
202+
203+def capture():
204+ results = []
205+ def callback(oops):
206+ results.append(oops)
207+ return [str(len(results))]
208+ return results, callback
209+
210+
211+def test_app():
212+ config = Config()
213+ app = make_app(config)
214+ events, publisher = capture()
215+ config.publisher = publisher
216+ # discard on-create hooks for deterministic test data.
217+ config.on_create = []
218+ def start_response(status, headers):
219+ events.append((status, headers))
220+ return events.append
221+ return app, events, start_response
222+
223+
224+
225+class TestApp(TestCase):
226+
227+ def test_not_get_post(self):
228+ app, events, start_response = test_app()
229+ environ = {
230+ 'PATH_INFO': '/',
231+ 'REQUEST_METHOD': 'FOO',
232+ }
233+ output = list(app(environ, start_response))
234+ self.assertThat(output, Equals([]))
235+ self.assertThat(events,
236+ Equals([('405 Method Not Allowed', [('Allow', 'GET, POST')])]))
237+
238+ def test_post_empty(self):
239+ # Empty generates a error response.
240+ app, events, start_response = test_app()
241+ environ = {
242+ 'PATH_INFO': '/',
243+ 'REQUEST_METHOD': 'POST',
244+ 'CONTENT_LENGTH': '0',
245+ 'wsgi.input': StringIO(),
246+ }
247+ output = list(app(environ, start_response))
248+ self.assertThat(output, Equals([]))
249+ self.assertThat(events,
250+ Equals([('400 Empty OOPS report', [])]))
251+
252+ def test_post_nonjson(self):
253+ # Posting random crap is caught.
254+ app, events, start_response = test_app()
255+ environ = {
256+ 'PATH_INFO': '/',
257+ 'REQUEST_METHOD': 'POST',
258+ 'CONTENT_LENGTH': '12',
259+ 'wsgi.input': StringIO('[random junk'),
260+ }
261+ output = list(app(environ, start_response))
262+ self.assertThat(output, Equals([]))
263+ self.assertThat(events,
264+ Equals([('400 Bad report - invalid JSON', [])]))
265+
266+ def test_post_json(self):
267+ app, events, start_response = test_app()
268+ environ = {
269+ 'PATH_INFO': '/',
270+ 'REQUEST_METHOD': 'POST',
271+ 'CONTENT_LENGTH': '20',
272+ 'wsgi.input': StringIO('{"reporter": "fred"}'),
273+ }
274+ output = list(app(environ, start_response))
275+ self.assertThat(output, Equals(['']))
276+ self.assertThat(events,
277+ Equals([{'id': '1', u'reporter': u'fred'},
278+ ('200 OK', [('X-OOPS-ID', '1')])]))
279+
280
281=== modified file 'setup.py'
282--- setup.py 2012-08-09 23:57:35 +0000
283+++ setup.py 2012-09-09 04:11:19 +0000
284@@ -48,6 +48,7 @@
285 extras_require = dict(
286 test=[
287 'testtools',
288+ 'python-subunit',
289 ]
290 ),
291 )
292
293=== modified file 'versions.cfg'
294--- versions.cfg 2012-08-10 03:57:06 +0000
295+++ versions.cfg 2012-09-09 04:11:19 +0000
296@@ -20,6 +20,7 @@
297 oops-wsgi = 0.0.10
298 paste = 1.7.2
299 pymongo = 2.1.1
300+python-subunit = 0.0.8
301 pytz = 2012c
302 setuptools = 0.6c11
303 simplejson = 2.2.1

Subscribers

People subscribed via source and target branches

to all changes: