Merge lp:~cjwatson/launchpad/explicit-proxy-bugzilla into lp:launchpad
- explicit-proxy-bugzilla
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18700 |
Proposed branch: | lp:~cjwatson/launchpad/explicit-proxy-bugzilla |
Merge into: | lp:launchpad |
Prerequisite: | lp:~cjwatson/launchpad/explicit-proxy-mantis |
Diff against target: |
986 lines (+393/-138) 12 files modified
lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt (+3/-3) lib/lp/bugs/doc/externalbugtracker-bugzilla-lp-plugin.txt (+3/-3) lib/lp/bugs/doc/externalbugtracker-bugzilla-oddities.txt (+24/-10) lib/lp/bugs/doc/externalbugtracker-bugzilla.txt (+45/-35) lib/lp/bugs/externalbugtracker/bugzilla.py (+10/-9) lib/lp/bugs/externalbugtracker/tests/test_bugzilla.py (+18/-20) lib/lp/bugs/externalbugtracker/tests/test_xmlrpc.py (+45/-2) lib/lp/bugs/externalbugtracker/xmlrpc.py (+70/-2) lib/lp/bugs/tests/bugzilla-api-xmlrpc-transport.txt (+3/-3) lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt (+3/-3) lib/lp/bugs/tests/externalbugtracker-xmlrpc-transport.txt (+122/-0) lib/lp/bugs/tests/externalbugtracker.py (+47/-48) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/explicit-proxy-bugzilla |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+348432@code.launchpad.net |
Commit message
Convert the Bugzilla external bug tracker to urlfetch.
Description of the change
I had to write a new XML-RPC transport that uses requests. It's a relatively small variation on the urllib2-based one, and I'll delete that once I've converted its other user.
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt' |
2 | --- lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt 2018-06-05 12:18:58 +0000 |
3 | +++ lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt 2018-06-23 00:58:53 +0000 |
4 | @@ -51,9 +51,9 @@ |
5 | The authorisation cookie will be stored in the auth_cookie property of |
6 | the XML-RPC transport. |
7 | |
8 | - >>> test_transport.cookie_processor.cookiejar |
9 | - <...CookieJar[Cookie(version=0, name='Bugzilla_login'...), |
10 | - Cookie(version=0, name='Bugzilla_logincookie'...)]> |
11 | + >>> test_transport.cookie_jar |
12 | + <RequestsCookieJar[Cookie(version=0, name='Bugzilla_login'...), |
13 | + Cookie(version=0, name='Bugzilla_logincookie'...)]> |
14 | |
15 | Trying to log in to a Bugzilla instance for which we have no credentials |
16 | will raise an error: |
17 | |
18 | === modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla-lp-plugin.txt' |
19 | --- lib/lp/bugs/doc/externalbugtracker-bugzilla-lp-plugin.txt 2018-06-05 12:18:58 +0000 |
20 | +++ lib/lp/bugs/doc/externalbugtracker-bugzilla-lp-plugin.txt 2018-06-23 00:58:53 +0000 |
21 | @@ -78,9 +78,9 @@ |
22 | The authorisation cookie will be stored in the auth_cookie property of |
23 | the XML-RPC transport. |
24 | |
25 | - >>> test_transport.cookie_processor.cookiejar |
26 | - <...CookieJar[Cookie(version=0, name='Bugzilla_login'...), |
27 | - Cookie(version=0, name='Bugzilla_logincookie'...)]> |
28 | + >>> test_transport.cookie_jar |
29 | + <RequestsCookieJar[Cookie(version=0, name='Bugzilla_login'...), |
30 | + Cookie(version=0, name='Bugzilla_logincookie'...)]> |
31 | |
32 | The externalbugtracker.bugzilla module contains a decorator, |
33 | needs_authentication, which can be used to ensure that a |
34 | |
35 | === modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla-oddities.txt' |
36 | --- lib/lp/bugs/doc/externalbugtracker-bugzilla-oddities.txt 2012-04-12 11:38:44 +0000 |
37 | +++ lib/lp/bugs/doc/externalbugtracker-bugzilla-oddities.txt 2018-06-23 00:58:53 +0000 |
38 | @@ -21,7 +21,9 @@ |
39 | >>> txn = LaunchpadZopelessLayer.txn |
40 | >>> mozilla_bugzilla = getUtility(IBugTrackerSet).getByName('mozilla.org') |
41 | >>> issuezilla = TestIssuezilla(mozilla_bugzilla.baseurl) |
42 | - >>> issuezilla._probe_version() |
43 | + >>> transaction.commit() |
44 | + >>> with issuezilla.responses(post=False): |
45 | + ... issuezilla._probe_version() |
46 | (2, 11) |
47 | >>> for bug_watch in mozilla_bugzilla.watches: |
48 | ... print "%s: %s %s" % (bug_watch.remotebug, |
49 | @@ -32,8 +34,9 @@ |
50 | 42: FUBAR BAZBAZ |
51 | >>> transaction.commit() |
52 | >>> bug_watch_updater = CheckwatchesMaster(txn, logger=FakeLogger()) |
53 | - >>> bug_watch_updater.updateBugWatches( |
54 | - ... issuezilla, mozilla_bugzilla.watches) |
55 | + >>> with issuezilla.responses(): |
56 | + ... bug_watch_updater.updateBugWatches( |
57 | + ... issuezilla, mozilla_bugzilla.watches) |
58 | INFO Updating 4 watches for 3 bugs on https://bugzilla.mozilla.org |
59 | INFO Didn't find bug u'42' on https://bugzilla.mozilla.org |
60 | (local bugs: 1, 2). |
61 | @@ -58,7 +61,9 @@ |
62 | |
63 | a) The version is way old: |
64 | |
65 | - >>> old_bugzilla._probe_version() |
66 | + >>> transaction.commit() |
67 | + >>> with old_bugzilla.responses(post=False): |
68 | + ... old_bugzilla._probe_version() |
69 | (2, 10) |
70 | |
71 | b) The tags are not prefixed with the bz: namespace: |
72 | @@ -72,7 +77,8 @@ |
73 | We support them just fine: |
74 | |
75 | >>> remote_bugs = ['42', '123543'] |
76 | - >>> old_bugzilla.initializeRemoteBugDB(remote_bugs) |
77 | + >>> with old_bugzilla.responses(): |
78 | + ... old_bugzilla.initializeRemoteBugDB(remote_bugs) |
79 | >>> for remote_bug in remote_bugs: |
80 | ... print "%s: %s %s" % ( |
81 | ... remote_bug, |
82 | @@ -90,7 +96,9 @@ |
83 | >>> from lp.bugs.tests.externalbugtracker import ( |
84 | ... TestWeirdBugzilla) |
85 | >>> weird_bugzilla = TestWeirdBugzilla(mozilla_bugzilla.baseurl) |
86 | - >>> weird_bugzilla._probe_version() |
87 | + >>> transaction.commit() |
88 | + >>> with weird_bugzilla.responses(post=False): |
89 | + ... weird_bugzilla._probe_version() |
90 | (2, 20) |
91 | |
92 | a) The bug status tag is <bz:status> and not <bz:bug_status> |
93 | @@ -109,7 +117,8 @@ |
94 | Yet everything still works as expected: |
95 | |
96 | >>> remote_bugs = ['2000', '123543'] |
97 | - >>> weird_bugzilla.initializeRemoteBugDB(remote_bugs) |
98 | + >>> with weird_bugzilla.responses(): |
99 | + ... weird_bugzilla.initializeRemoteBugDB(remote_bugs) |
100 | >>> for remote_bug in remote_bugs: |
101 | ... print "%s: %s %s" % ( |
102 | ... remote_bug, |
103 | @@ -128,13 +137,16 @@ |
104 | >>> from lp.bugs.tests.externalbugtracker import ( |
105 | ... TestBrokenBugzilla) |
106 | >>> broken_bugzilla = TestBrokenBugzilla(mozilla_bugzilla.baseurl) |
107 | - >>> broken_bugzilla._probe_version() |
108 | + >>> transaction.commit() |
109 | + >>> with broken_bugzilla.responses(post=False): |
110 | + ... broken_bugzilla._probe_version() |
111 | (2, 20) |
112 | >>> "</foobar>" in broken_bugzilla._readBugItemFile() |
113 | True |
114 | |
115 | >>> remote_bugs = ['42', '2000'] |
116 | - >>> broken_bugzilla.initializeRemoteBugDB(remote_bugs) |
117 | + >>> with broken_bugzilla.responses(): |
118 | + ... broken_bugzilla.initializeRemoteBugDB(remote_bugs) |
119 | Traceback (most recent call last): |
120 | ... |
121 | UnparsableBugData: Failed to parse XML description... |
122 | @@ -148,4 +160,6 @@ |
123 | True |
124 | |
125 | >>> remote_bugs = ['42', '2000'] |
126 | - >>> broken_bugzilla.initializeRemoteBugDB(remote_bugs) # no exception |
127 | + >>> transaction.commit() |
128 | + >>> with broken_bugzilla.responses(): |
129 | + ... broken_bugzilla.initializeRemoteBugDB(remote_bugs) # no exception |
130 | |
131 | === modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla.txt' |
132 | --- lib/lp/bugs/doc/externalbugtracker-bugzilla.txt 2017-10-24 08:11:47 +0000 |
133 | +++ lib/lp/bugs/doc/externalbugtracker-bugzilla.txt 2018-06-23 00:58:53 +0000 |
134 | @@ -63,7 +63,9 @@ |
135 | >>> gnome_bugzilla = ( |
136 | ... getUtility(IBugTrackerSet).getByName('gnome-bugzilla')) |
137 | >>> external_bugzilla = TestBugzilla(gnome_bugzilla.baseurl) |
138 | - >>> version = external_bugzilla._probe_version() |
139 | + >>> transaction.commit() |
140 | + >>> with external_bugzilla.responses(post=False): |
141 | + ... version = external_bugzilla._probe_version() |
142 | >>> version |
143 | (2, 20) |
144 | |
145 | @@ -459,8 +461,9 @@ |
146 | ... bug_watch.remote_importance) |
147 | 304070: None None |
148 | 3224: None |
149 | - >>> bug_watch_updater.updateBugWatches( |
150 | - ... external_bugzilla, gnome_bugzilla.watches) |
151 | + >>> with external_bugzilla.responses(): |
152 | + ... bug_watch_updater.updateBugWatches( |
153 | + ... external_bugzilla, gnome_bugzilla.watches) |
154 | INFO Updating 2 watches for 2 bugs on http://bugzilla.gnome.org/bugs |
155 | INFO Didn't find bug u'304070' on |
156 | http://bugzilla.gnome.org/bugs (local bugs: 15). |
157 | @@ -503,19 +506,19 @@ |
158 | |
159 | Then updateBugWatches() will make one request per bug watch: |
160 | |
161 | - >>> external_bugzilla.trace_calls = True |
162 | - >>> bug_watch_updater.updateBugWatches( |
163 | - ... external_bugzilla, gnome_bugzilla.watches) |
164 | + >>> with external_bugzilla.responses(trace_calls=True, get=False): |
165 | + ... bug_watch_updater.updateBugWatches( |
166 | + ... external_bugzilla, gnome_bugzilla.watches) |
167 | INFO Updating 7 watches for 7 bugs on http://bugzilla.gnome.org/bugs |
168 | - CALLED _postPage() |
169 | - CALLED _postPage() |
170 | - CALLED _postPage() |
171 | - CALLED _postPage() |
172 | - CALLED _postPage() |
173 | - CALLED _postPage() |
174 | - CALLED _postPage() |
175 | INFO Didn't find bug u'304070' on |
176 | http://bugzilla.gnome.org/bugs (local bugs: 15). |
177 | + POST http://bugzilla.gnome.org/bugs/buglist.cgi |
178 | + POST http://bugzilla.gnome.org/bugs/buglist.cgi |
179 | + POST http://bugzilla.gnome.org/bugs/buglist.cgi |
180 | + POST http://bugzilla.gnome.org/bugs/buglist.cgi |
181 | + POST http://bugzilla.gnome.org/bugs/buglist.cgi |
182 | + POST http://bugzilla.gnome.org/bugs/buglist.cgi |
183 | + POST http://bugzilla.gnome.org/bugs/buglist.cgi |
184 | |
185 | >>> remote_statuses = dict( |
186 | ... [(int(bug_watch.remotebug), bug_watch.remotestatus) |
187 | @@ -529,8 +532,6 @@ |
188 | >>> remote_importances == expected_remote_importances |
189 | True |
190 | |
191 | - >>> external_bugzilla.trace_calls = False |
192 | - |
193 | Let's add a few more watches: |
194 | |
195 | >>> expected_remote_statuses = dict( |
196 | @@ -557,13 +558,13 @@ |
197 | Instead of issuing one request per bug watch, like was done before, |
198 | updateBugWatches() issues only one request to update all watches: |
199 | |
200 | - >>> external_bugzilla.trace_calls = True |
201 | - >>> bug_watch_updater.updateBugWatches( |
202 | - ... external_bugzilla, gnome_bugzilla.watches) |
203 | + >>> with external_bugzilla.responses(trace_calls=True, get=False): |
204 | + ... bug_watch_updater.updateBugWatches( |
205 | + ... external_bugzilla, gnome_bugzilla.watches) |
206 | INFO Updating 207 watches for 207 bugs... |
207 | - CALLED _postPage() |
208 | INFO Didn't find bug u'304070' on |
209 | http://bugzilla.gnome.org/bugs (local bugs: 15). |
210 | + POST http://bugzilla.gnome.org/bugs/buglist.cgi |
211 | |
212 | >>> remote_statuses = dict( |
213 | ... [(int(bug_watch.remotebug), bug_watch.remotestatus) |
214 | @@ -577,8 +578,6 @@ |
215 | >>> remote_importances == expected_remote_importances |
216 | True |
217 | |
218 | - >>> external_bugzilla.trace_calls = False |
219 | - |
220 | updateBugWatches() updates the lastchecked attribute on the watches, so |
221 | now no bug watches are in need of updating: |
222 | |
223 | @@ -596,7 +595,8 @@ |
224 | >>> now = datetime.now(pytz.timezone('UTC')) |
225 | >>> bug_watch.lastchanged = now - timedelta(weeks=2) |
226 | >>> old_last_changed = bug_watch.lastchanged |
227 | - >>> bug_watch_updater.updateBugWatches(external_bugzilla, [bug_watch]) |
228 | + >>> with external_bugzilla.responses(get=False): |
229 | + ... bug_watch_updater.updateBugWatches(external_bugzilla, [bug_watch]) |
230 | INFO Updating 1 watches for 1 bugs on http://bugzilla.gnome.org/bugs |
231 | >>> bug_watch.lastchanged == old_last_changed |
232 | True |
233 | @@ -632,8 +632,9 @@ |
234 | Let's update the bug watch, and see that the linked bug watch got |
235 | synced: |
236 | |
237 | - >>> bug_watch_updater.updateBugWatches( |
238 | - ... external_bugzilla, [thunderbird_task.bugwatch]) |
239 | + >>> with external_bugzilla.responses(get=False): |
240 | + ... bug_watch_updater.updateBugWatches( |
241 | + ... external_bugzilla, [thunderbird_task.bugwatch]) |
242 | INFO Updating 1 watches for 1 bugs on https://bugzilla.mozilla.org |
243 | |
244 | >>> bug_nine = getUtility(IBugSet).get(9) |
245 | @@ -655,8 +656,9 @@ |
246 | >>> thunderbird_task.transitionToStatus( |
247 | ... BugTaskStatus.CONFIRMED, |
248 | ... getUtility(IPersonSet).getByName('no-priv')) |
249 | - >>> bug_watch_updater.updateBugWatches( |
250 | - ... external_bugzilla, [thunderbird_task.bugwatch]) |
251 | + >>> with external_bugzilla.responses(get=False): |
252 | + ... bug_watch_updater.updateBugWatches( |
253 | + ... external_bugzilla, [thunderbird_task.bugwatch]) |
254 | INFO Updating 1 watches for 1 bugs on https://bugzilla.mozilla.org |
255 | |
256 | >>> bug_nine = getUtility(IBugSet).get(9) |
257 | @@ -684,8 +686,9 @@ |
258 | ... bug=bug_two, owner=sample_person, bugtracker=mozilla_bugzilla, |
259 | ... remotebug='42') |
260 | >>> bug_watch2_id = bug_watch2.id |
261 | - >>> bug_watch_updater.updateBugWatches( |
262 | - ... external_bugzilla, [bug_watch1, bug_watch2]) |
263 | + >>> with external_bugzilla.responses(get=False): |
264 | + ... bug_watch_updater.updateBugWatches( |
265 | + ... external_bugzilla, [bug_watch1, bug_watch2]) |
266 | INFO Updating 2 watches for 1 bugs on https://bugzilla.mozilla.org |
267 | |
268 | >>> bug_watch1 = getUtility(IBugWatchSet).get(bug_watch1_id) |
269 | @@ -702,10 +705,12 @@ |
270 | If updateBugWatches() can't parse the XML file returned from the remote |
271 | bug tracker, an error is logged. |
272 | |
273 | - >>> external_bugzilla._postPage = ( |
274 | - ... lambda self, data, repost_on_redirect: '<invalid xml>') |
275 | - >>> bug_watch_updater.updateBugWatches( |
276 | - ... external_bugzilla, [bug_watch1, bug_watch2]) |
277 | + >>> import re |
278 | + >>> with external_bugzilla.responses() as requests_mock: |
279 | + ... requests_mock.reset() |
280 | + ... requests_mock.add('POST', re.compile(r'.*'), body='<invalid xml>') |
281 | + ... bug_watch_updater.updateBugWatches( |
282 | + ... external_bugzilla, [bug_watch1, bug_watch2]) |
283 | Traceback (most recent call last): |
284 | ... |
285 | UnparsableBugData: |
286 | @@ -730,10 +735,13 @@ |
287 | initializeRemoteBugDB() has been called, in order for the bug |
288 | information to be fetched from the external Bugzilla instance. |
289 | |
290 | + >>> transaction.commit() |
291 | + |
292 | >>> external_bugzilla = TestBugzilla() |
293 | >>> external_bugzilla.bugzilla_bugs = {84: ( |
294 | ... 'RESOLVED', 'FIXED', 'MEDIUM', 'NORMAL')} |
295 | - >>> external_bugzilla.initializeRemoteBugDB(['84']) |
296 | + >>> with external_bugzilla.responses(): |
297 | + ... external_bugzilla.initializeRemoteBugDB(['84']) |
298 | >>> external_bugzilla.remote_bug_product['84'] |
299 | u'product-84' |
300 | >>> external_bugzilla.getRemoteProduct('84') |
301 | @@ -747,7 +755,8 @@ |
302 | ... 'RESOLVED', 'FIXED', 'MEDIUM', 'NORMAL')} |
303 | >>> # Make the buglist XML not include the product tag. |
304 | >>> external_bugzilla.bug_item_file = 'gnome_bug_li_item_noproduct.xml' |
305 | - >>> external_bugzilla.initializeRemoteBugDB(['84']) |
306 | + >>> with external_bugzilla.responses(): |
307 | + ... external_bugzilla.initializeRemoteBugDB(['84']) |
308 | >>> print external_bugzilla.getRemoteProduct('84') |
309 | None |
310 | |
311 | @@ -756,7 +765,8 @@ |
312 | >>> external_bugzilla = TestBugzilla() |
313 | >>> external_bugzilla.bugzilla_bugs = {84: ( |
314 | ... 'RESOLVED', 'FIXED', 'MEDIUM', 'NORMAL')} |
315 | - >>> external_bugzilla.initializeRemoteBugDB(['84']) |
316 | + >>> with external_bugzilla.responses(): |
317 | + ... external_bugzilla.initializeRemoteBugDB(['84']) |
318 | >>> external_bugzilla.getRemoteProduct('42') |
319 | Traceback (most recent call last): |
320 | ... |
321 | |
322 | === modified file 'lib/lp/bugs/externalbugtracker/bugzilla.py' |
323 | --- lib/lp/bugs/externalbugtracker/bugzilla.py 2018-06-05 12:18:58 +0000 |
324 | +++ lib/lp/bugs/externalbugtracker/bugzilla.py 2018-06-23 00:58:53 +0000 |
325 | @@ -15,12 +15,12 @@ |
326 | from httplib import BadStatusLine |
327 | import re |
328 | import string |
329 | -from urllib2 import URLError |
330 | from xml.dom import minidom |
331 | import xml.parsers.expat |
332 | import xmlrpclib |
333 | |
334 | import pytz |
335 | +import requests |
336 | import six |
337 | from zope.component import getUtility |
338 | from zope.interface import ( |
339 | @@ -32,7 +32,7 @@ |
340 | BugNotFound, |
341 | BugTrackerAuthenticationError, |
342 | BugTrackerConnectError, |
343 | - ExternalBugTracker, |
344 | + ExternalBugTrackerRequests, |
345 | InvalidBugId, |
346 | LookupTree, |
347 | UnknownRemoteImportanceError, |
348 | @@ -40,7 +40,7 @@ |
349 | UnparsableBugData, |
350 | UnparsableBugTrackerVersion, |
351 | ) |
352 | -from lp.bugs.externalbugtracker.xmlrpc import UrlLib2Transport |
353 | +from lp.bugs.externalbugtracker.xmlrpc import RequestsTransport |
354 | from lp.bugs.interfaces.bugtask import ( |
355 | BugTaskImportance, |
356 | BugTaskStatus, |
357 | @@ -61,8 +61,8 @@ |
358 | ) |
359 | |
360 | |
361 | -class Bugzilla(ExternalBugTracker): |
362 | - """An ExternalBugTrack for dealing with remote Bugzilla systems.""" |
363 | +class Bugzilla(ExternalBugTrackerRequests): |
364 | + """An ExternalBugTracker for dealing with remote Bugzilla systems.""" |
365 | |
366 | batch_query_threshold = 0 # Always use the batch method. |
367 | _test_xmlrpc_proxy = None |
368 | @@ -171,7 +171,8 @@ |
369 | return BugzillaLPPlugin(self.baseurl) |
370 | elif self._remoteSystemHasBugzillaAPI(): |
371 | return BugzillaAPI(self.baseurl) |
372 | - except (xmlrpclib.ProtocolError, URLError, BadStatusLine): |
373 | + except (xmlrpclib.ProtocolError, requests.RequestException, |
374 | + BadStatusLine): |
375 | pass |
376 | return self |
377 | |
378 | @@ -201,7 +202,7 @@ |
379 | server cannot be reached `BugTrackerConnectError` will be |
380 | raised. |
381 | """ |
382 | - version_xml = self._getPage('xml.cgi?id=1') |
383 | + version_xml = self._getPage('xml.cgi?id=1').content |
384 | try: |
385 | document = self._parseDOMString(version_xml) |
386 | except xml.parsers.expat.ExpatError as e: |
387 | @@ -410,7 +411,7 @@ |
388 | severity_tag = 'bz:bug_severity' |
389 | |
390 | buglist_xml = self._postPage( |
391 | - buglist_page, data, repost_on_redirect=True) |
392 | + buglist_page, data, repost_on_redirect=True).content |
393 | |
394 | try: |
395 | document = self._parseDOMString(buglist_xml) |
396 | @@ -568,7 +569,7 @@ |
397 | |
398 | self.internal_xmlrpc_transport = internal_xmlrpc_transport |
399 | if xmlrpc_transport is None: |
400 | - self.xmlrpc_transport = UrlLib2Transport(self.xmlrpc_endpoint) |
401 | + self.xmlrpc_transport = RequestsTransport(self.xmlrpc_endpoint) |
402 | else: |
403 | self.xmlrpc_transport = xmlrpc_transport |
404 | |
405 | |
406 | === modified file 'lib/lp/bugs/externalbugtracker/tests/test_bugzilla.py' |
407 | --- lib/lp/bugs/externalbugtracker/tests/test_bugzilla.py 2012-01-01 02:58:52 +0000 |
408 | +++ lib/lp/bugs/externalbugtracker/tests/test_bugzilla.py 2018-06-23 00:58:53 +0000 |
409 | @@ -1,14 +1,14 @@ |
410 | -# Copyright 2010-2011 Canonical Ltd. This software is licensed under the |
411 | +# Copyright 2010-2018 Canonical Ltd. This software is licensed under the |
412 | # GNU Affero General Public License version 3 (see the file LICENSE). |
413 | |
414 | """Tests for the Bugzilla BugTracker.""" |
415 | |
416 | __metaclass__ = type |
417 | |
418 | -from StringIO import StringIO |
419 | from xml.parsers.expat import ExpatError |
420 | import xmlrpclib |
421 | |
422 | +import responses |
423 | import transaction |
424 | |
425 | from lp.bugs.externalbugtracker.base import UnparsableBugData |
426 | @@ -17,7 +17,6 @@ |
427 | TestCase, |
428 | TestCaseWithFactory, |
429 | ) |
430 | -from lp.testing.fakemethod import FakeMethod |
431 | from lp.testing.layers import ZopelessLayer |
432 | |
433 | |
434 | @@ -28,28 +27,26 @@ |
435 | |
436 | base_url = "http://example.com/" |
437 | |
438 | - def _makeInstrumentedBugzilla(self, page=None, content=None): |
439 | - """Create a `Bugzilla` with a fake urlopen.""" |
440 | - if page is None: |
441 | - page = self.factory.getUniqueString() |
442 | - bugzilla = Bugzilla(self.base_url) |
443 | - if content is None: |
444 | - content = "<bugzilla>%s</bugzilla>" % ( |
445 | - self.factory.getUniqueString()) |
446 | - fake_page = StringIO(content) |
447 | - fake_page.url = self.base_url + page |
448 | - bugzilla.urlopen = FakeMethod(result=fake_page) |
449 | - return bugzilla |
450 | - |
451 | + @responses.activate |
452 | def test_post_to_search_form_does_not_crash(self): |
453 | - page = self.factory.getUniqueString() |
454 | - bugzilla = self._makeInstrumentedBugzilla(page) |
455 | + responses.add( |
456 | + "POST", self.base_url + "xml.cgi", |
457 | + body="<bugzilla>%s</bugzilla>" % self.factory.getUniqueString()) |
458 | + bugzilla = Bugzilla(self.base_url) |
459 | bugzilla.getRemoteBugBatch([]) |
460 | |
461 | + @responses.activate |
462 | def test_repost_on_redirect_does_not_crash(self): |
463 | - bugzilla = self._makeInstrumentedBugzilla() |
464 | + responses.add( |
465 | + "POST", self.base_url + "xml.cgi", status=302, |
466 | + headers={"Location": self.base_url + "buglist.cgi"}) |
467 | + responses.add( |
468 | + "POST", self.base_url + "buglist.cgi", |
469 | + body="<bugzilla>%s</bugzilla>" % self.factory.getUniqueString()) |
470 | + bugzilla = Bugzilla(self.base_url) |
471 | bugzilla.getRemoteBugBatch([]) |
472 | |
473 | + @responses.activate |
474 | def test_reports_invalid_search_result(self): |
475 | # Sometimes bug searches may go wrong, yielding an HTML page |
476 | # instead. getRemoteBugBatch rejects and reports search results |
477 | @@ -61,7 +58,8 @@ |
478 | </body> |
479 | </html> |
480 | """ |
481 | - bugzilla = self._makeInstrumentedBugzilla(content=result_text) |
482 | + responses.add("POST", self.base_url + "xml.cgi", body=result_text) |
483 | + bugzilla = Bugzilla(self.base_url) |
484 | self.assertRaises(UnparsableBugData, bugzilla.getRemoteBugBatch, []) |
485 | |
486 | |
487 | |
488 | === modified file 'lib/lp/bugs/externalbugtracker/tests/test_xmlrpc.py' |
489 | --- lib/lp/bugs/externalbugtracker/tests/test_xmlrpc.py 2018-04-05 16:18:34 +0000 |
490 | +++ lib/lp/bugs/externalbugtracker/tests/test_xmlrpc.py 2018-06-23 00:58:53 +0000 |
491 | @@ -1,4 +1,4 @@ |
492 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
493 | +# Copyright 2011-2018 Canonical Ltd. This software is licensed under the |
494 | # GNU Affero General Public License version 3 (see the file LICENSE). |
495 | |
496 | """Tests for `lp.bugs.externalbugtracker.xmlrpc`.""" |
497 | @@ -10,8 +10,13 @@ |
498 | from xml.parsers.expat import ExpatError |
499 | |
500 | from fixtures import MockPatch |
501 | +import requests |
502 | +import responses |
503 | |
504 | -from lp.bugs.externalbugtracker.xmlrpc import UrlLib2Transport |
505 | +from lp.bugs.externalbugtracker.xmlrpc import ( |
506 | + RequestsTransport, |
507 | + UrlLib2Transport, |
508 | + ) |
509 | from lp.bugs.tests.externalbugtracker import ( |
510 | ensure_response_parser_is_expat, |
511 | UrlLib2TransportTestHandler, |
512 | @@ -51,3 +56,41 @@ |
513 | URLError, '<urlopen error [Errno -2] Name or service not known>', |
514 | transport.request, u"test.invalid", u"xmlrpc", |
515 | u"\N{SNOWMAN}".encode('utf-8')) |
516 | + |
517 | + |
518 | +class TestRequestsTransport(TestCase): |
519 | + """Tests for `RequestsTransport`.""" |
520 | + |
521 | + @responses.activate |
522 | + def test_expat_error(self): |
523 | + # Malformed XML-RPC responses cause xmlrpclib to raise an ExpatError. |
524 | + responses.add( |
525 | + "POST", "http://www.example.com/xmlrpc", |
526 | + body="<params><mis></match></params>") |
527 | + transport = RequestsTransport("http://not.real/") |
528 | + |
529 | + # The Launchpad production environment selects Expat at present. This |
530 | + # is quite strict compared to the other parsers that xmlrpclib can |
531 | + # possibly select. |
532 | + ensure_response_parser_is_expat(transport) |
533 | + |
534 | + self.assertRaises( |
535 | + ExpatError, transport.request, |
536 | + 'www.example.com', 'xmlrpc', "<methodCall />") |
537 | + |
538 | + def test_unicode_url(self): |
539 | + # Python's httplib doesn't like Unicode URLs much. Ensure that |
540 | + # they don't cause it to crash, and we get a post-serialisation |
541 | + # connection error instead. |
542 | + self.useFixture(MockPatch( |
543 | + "socket.getaddrinfo", |
544 | + side_effect=socket.gaierror( |
545 | + socket.EAI_NONAME, "Name or service not known"))) |
546 | + transport = RequestsTransport(u"http://test.invalid/") |
547 | + for proxy in (None, "http://squid.internal:3128/"): |
548 | + self.pushConfig("launchpad", http_proxy=proxy) |
549 | + e = self.assertRaises( |
550 | + requests.ConnectionError, |
551 | + transport.request, u"test.invalid", u"xmlrpc", |
552 | + u"\N{SNOWMAN}".encode('utf-8')) |
553 | + self.assertIn("Name or service not known", str(e)) |
554 | |
555 | === modified file 'lib/lp/bugs/externalbugtracker/xmlrpc.py' |
556 | --- lib/lp/bugs/externalbugtracker/xmlrpc.py 2015-07-10 07:48:06 +0000 |
557 | +++ lib/lp/bugs/externalbugtracker/xmlrpc.py 2018-06-23 00:58:53 +0000 |
558 | @@ -1,10 +1,11 @@ |
559 | -# Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
560 | +# Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
561 | # GNU Affero General Public License version 3 (see the file LICENSE). |
562 | |
563 | -"""An XMLRPC transport which uses urllib2.""" |
564 | +"""XMLRPC transports which use urllib2 or requests.""" |
565 | |
566 | __metaclass__ = type |
567 | __all__ = [ |
568 | + 'RequestsTransport', |
569 | 'UrlLib2Transport', |
570 | 'XMLRPCRedirectHandler', |
571 | ] |
572 | @@ -12,6 +13,7 @@ |
573 | |
574 | from cookielib import Cookie |
575 | from cStringIO import StringIO |
576 | +from io import BytesIO |
577 | from urllib2 import ( |
578 | build_opener, |
579 | HTTPCookieProcessor, |
580 | @@ -28,7 +30,15 @@ |
581 | Transport, |
582 | ) |
583 | |
584 | +import requests |
585 | +from requests.cookies import RequestsCookieJar |
586 | + |
587 | +from lp.bugs.externalbugtracker.base import repost_on_redirect_hook |
588 | from lp.services.config import config |
589 | +from lp.services.timeout import ( |
590 | + override_timeout, |
591 | + urlfetch, |
592 | + ) |
593 | from lp.services.utils import traceback_info |
594 | |
595 | |
596 | @@ -122,3 +132,61 @@ |
597 | else: |
598 | traceback_info(response) |
599 | return self.parse_response(StringIO(response)) |
600 | + |
601 | + |
602 | +class RequestsTransport(Transport): |
603 | + """An XML-RPC transport which uses requests. |
604 | + |
605 | + This XML-RPC transport uses the Python requests module to make the |
606 | + request. (In fact, it uses lp.services.timeout.urlfetch, which wraps |
607 | + requests and deals with timeout handling.) |
608 | + |
609 | + Note: this transport isn't fit for general XML-RPC use. It is just good |
610 | + enough for some of our external bug tracker implementations. |
611 | + |
612 | + :param endpoint: The URL of the XML-RPC server. |
613 | + """ |
614 | + |
615 | + verbose = False |
616 | + |
617 | + def __init__(self, endpoint, cookie_jar=None): |
618 | + Transport.__init__(self, use_datetime=True) |
619 | + self.scheme, self.host = urlparse(endpoint)[:2] |
620 | + assert self.scheme in ('http', 'https'), ( |
621 | + "Unsupported URL scheme: %s" % self.scheme) |
622 | + if cookie_jar is None: |
623 | + cookie_jar = RequestsCookieJar() |
624 | + self.cookie_jar = cookie_jar |
625 | + self.timeout = config.checkwatches.default_socket_timeout |
626 | + |
627 | + def setCookie(self, cookie_str): |
628 | + """Set a cookie for the transport to use in future connections.""" |
629 | + name, value = cookie_str.split('=') |
630 | + self.cookie_jar.set( |
631 | + name, value, domain=self.host, path='', expires=False, |
632 | + discard=None, rest=None) |
633 | + |
634 | + def request(self, host, handler, request_body, verbose=0): |
635 | + """Make an XMLRPC request. |
636 | + |
637 | + Uses the configured proxy server to make the connection. |
638 | + """ |
639 | + url = urlunparse((self.scheme, host, handler, '', '', '')) |
640 | + # httplib can raise a UnicodeDecodeError when using a Unicode |
641 | + # URL, a non-ASCII body and a proxy. http://bugs.python.org/issue12398 |
642 | + if not isinstance(url, bytes): |
643 | + url = url.encode('utf-8') |
644 | + try: |
645 | + with override_timeout(self.timeout): |
646 | + response = urlfetch( |
647 | + url, method='POST', headers={'Content-Type': 'text/xml'}, |
648 | + data=request_body, cookies=self.cookie_jar, |
649 | + hooks={'response': repost_on_redirect_hook}, |
650 | + trust_env=False, use_proxy=True) |
651 | + except requests.HTTPError as e: |
652 | + raise ProtocolError( |
653 | + url.decode('utf-8'), e.response.status_code, e.response.reason, |
654 | + e.response.headers) |
655 | + else: |
656 | + traceback_info(response.text) |
657 | + return self.parse_response(BytesIO(response.content)) |
658 | |
659 | === modified file 'lib/lp/bugs/tests/bugzilla-api-xmlrpc-transport.txt' |
660 | --- lib/lp/bugs/tests/bugzilla-api-xmlrpc-transport.txt 2018-06-05 12:18:58 +0000 |
661 | +++ lib/lp/bugs/tests/bugzilla-api-xmlrpc-transport.txt 2018-06-23 00:58:53 +0000 |
662 | @@ -43,9 +43,9 @@ |
663 | |
664 | The Bugzilla_logincookie will now have been set for the transport, too. |
665 | |
666 | - >>> print bugzilla_transport.cookie_processor.cookiejar |
667 | - <...CookieJar[<Cookie Bugzilla_login=...>, |
668 | - <Cookie Bugzilla_logincookie=...>]> |
669 | + >>> print bugzilla_transport.cookie_jar |
670 | + <RequestsCookieJar[<Cookie Bugzilla_login=...>, |
671 | + <Cookie Bugzilla_logincookie=...>]> |
672 | |
673 | Trying to log in with an incorrect username or password will result in |
674 | an error being raised. |
675 | |
676 | === modified file 'lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt' |
677 | --- lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt 2018-06-05 12:18:58 +0000 |
678 | +++ lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt 2018-06-23 00:58:53 +0000 |
679 | @@ -90,9 +90,9 @@ |
680 | |
681 | The login cookies are in the transport's cookie jar. |
682 | |
683 | - >>> print bugzilla_transport.cookie_processor.cookiejar |
684 | - <...CookieJar[<Cookie Bugzilla_login=...>, |
685 | - <Cookie Bugzilla_logincookie=...>]> |
686 | + >>> print bugzilla_transport.cookie_jar |
687 | + <RequestsCookieJar[<Cookie Bugzilla_login=...>, |
688 | + <Cookie Bugzilla_logincookie=...>]> |
689 | |
690 | |
691 | Launchpad.time() |
692 | |
693 | === modified file 'lib/lp/bugs/tests/externalbugtracker-xmlrpc-transport.txt' |
694 | --- lib/lp/bugs/tests/externalbugtracker-xmlrpc-transport.txt 2016-09-21 02:49:42 +0000 |
695 | +++ lib/lp/bugs/tests/externalbugtracker-xmlrpc-transport.txt 2018-06-23 00:58:53 +0000 |
696 | @@ -193,3 +193,125 @@ |
697 | |
698 | >>> print redirected_request.data |
699 | None |
700 | + |
701 | + |
702 | +XMLRPC requests transport |
703 | +------------------------- |
704 | + |
705 | +When using XMLRPC for connecting to external bug trackers, we need to |
706 | +use a special transport, which processes http cookies correctly, and |
707 | +which can connect through an http proxy server. |
708 | + |
709 | + >>> from lp.bugs.externalbugtracker.xmlrpc import RequestsTransport |
710 | + |
711 | +RequestsTransport accepts a CookieJar as an optional parameter upon creation. |
712 | +This allows us to share a CookieJar - and therefore the cookie it contains - |
713 | +between different transports or URL openers. |
714 | + |
715 | + >>> from requests.cookies import RequestsCookieJar |
716 | + >>> jar = RequestsCookieJar() |
717 | + >>> transport = RequestsTransport('http://example.com', jar) |
718 | + >>> transport.cookie_jar == jar |
719 | + True |
720 | + |
721 | +We define a test response callback that returns the request-url and any |
722 | +request parameters as an XMLRPC parameter, and sets a cookie from the |
723 | +server, 'foo=bar'. |
724 | + |
725 | + >>> import responses |
726 | + >>> from six.moves import xmlrpc_client |
727 | + |
728 | + >>> def test_callback(request): |
729 | + ... params = xmlrpc_client.loads(request.body)[0] |
730 | + ... return ( |
731 | + ... 200, {'Set-Cookie': 'foo=bar'}, |
732 | + ... xmlrpc_client.dumps( |
733 | + ... ([request.url] + list(params),), methodresponse=True)) |
734 | + |
735 | +Before sending the request, the transport's cookie jar is empty. |
736 | + |
737 | + >>> def print_cookie_jar(jar): |
738 | + ... for name, value in sorted(jar.items()): |
739 | + ... print('%s=%s' % (name, value)) |
740 | + |
741 | + >>> print_cookie_jar(transport.cookie_jar) |
742 | + |
743 | + >>> request_body = """<?xml version="1.0"?> |
744 | + ... <methodCall> |
745 | + ... <methodName>examples.testMethod</methodName> |
746 | + ... <params> |
747 | + ... <param> |
748 | + ... <value> |
749 | + ... <int>42</int> |
750 | + ... </value> |
751 | + ... </param> |
752 | + ... </params> |
753 | + ... </methodCall> |
754 | + ... """ |
755 | + >>> with responses.RequestsMock() as requests_mock: |
756 | + ... requests_mock.add_callback( |
757 | + ... 'POST', 'http://www.example.com/xmlrpc', test_callback) |
758 | + ... transport.request('www.example.com', 'xmlrpc', request_body) |
759 | + (['http://www.example.com/xmlrpc', 42],) |
760 | + |
761 | +We received the url as the single XMLRPC result, and the cookie jar now |
762 | +contains the 'foo=bar' cookie sent by the server. |
763 | + |
764 | + >>> print_cookie_jar(transport.cookie_jar) |
765 | + foo=bar |
766 | + |
767 | +In addition to cookies sent by the server, we can set cookies locally. |
768 | + |
769 | + >>> transport.setCookie('ding=dong') |
770 | + >>> print_cookie_jar(transport.cookie_jar) |
771 | + ding=dong |
772 | + foo=bar |
773 | + |
774 | +If an error occurs trying to make the request, an |
775 | +``xmlrpclib.ProtocolError`` is raised. |
776 | + |
777 | + >>> request_body = """<?xml version="1.0"?> |
778 | + ... <methodCall> |
779 | + ... <methodName>examples.testError</methodName> |
780 | + ... <params> |
781 | + ... <param> |
782 | + ... <value> |
783 | + ... <int>42</int> |
784 | + ... </value> |
785 | + ... </param> |
786 | + ... </params> |
787 | + ... </methodCall> |
788 | + ... """ |
789 | + >>> with responses.RequestsMock() as requests_mock: |
790 | + ... requests_mock.add( |
791 | + ... 'POST', 'http://www.example.com/xmlrpc', status=500) |
792 | + ... transport.request('www.example.com', 'xmlrpc', request_body) |
793 | + Traceback (most recent call last): |
794 | + ... |
795 | + ProtocolError: <ProtocolError for http://www.example.com/xmlrpc: 500 |
796 | + Internal Server Error> |
797 | + |
798 | +If the transport encounters a redirect response it will make its request |
799 | +to the location indicated in that response rather than the original |
800 | +location. |
801 | + |
802 | + >>> request_body = """<?xml version="1.0"?> |
803 | + ... <methodCall> |
804 | + ... <methodName>examples.whatever</methodName> |
805 | + ... <params> |
806 | + ... <param> |
807 | + ... <value> |
808 | + ... <int>42</int> |
809 | + ... </value> |
810 | + ... </param> |
811 | + ... </params> |
812 | + ... </methodCall> |
813 | + ... """ |
814 | + >>> with responses.RequestsMock() as requests_mock: |
815 | + ... target_url = 'http://www.example.com/xmlrpc/redirected' |
816 | + ... requests_mock.add( |
817 | + ... 'POST', 'http://www.example.com/xmlrpc', status=302, |
818 | + ... headers={'Location': target_url}) |
819 | + ... requests_mock.add_callback('POST', target_url, test_callback) |
820 | + ... transport.request('www.example.com', 'xmlrpc', request_body) |
821 | + (['http://www.example.com/xmlrpc/redirected', 42],) |
822 | |
823 | === modified file 'lib/lp/bugs/tests/externalbugtracker.py' |
824 | --- lib/lp/bugs/tests/externalbugtracker.py 2018-06-23 00:58:53 +0000 |
825 | +++ lib/lp/bugs/tests/externalbugtracker.py 2018-06-23 00:58:53 +0000 |
826 | @@ -52,7 +52,10 @@ |
827 | LP_PLUGIN_METADATA_AND_COMMENTS, |
828 | LP_PLUGIN_METADATA_ONLY, |
829 | ) |
830 | -from lp.bugs.externalbugtracker.xmlrpc import UrlLib2Transport |
831 | +from lp.bugs.externalbugtracker.xmlrpc import ( |
832 | + RequestsTransport, |
833 | + UrlLib2Transport, |
834 | + ) |
835 | from lp.bugs.interfaces.bugtask import ( |
836 | BugTaskImportance, |
837 | BugTaskStatus, |
838 | @@ -260,12 +263,8 @@ |
839 | raise self.get_remote_status_error("Testing") |
840 | |
841 | |
842 | -class TestBugzilla(Bugzilla): |
843 | - """Bugzilla ExternalSystem for use in tests. |
844 | - |
845 | - It overrides _getPage and _postPage, so that access to a real Bugzilla |
846 | - instance isn't needed. |
847 | - """ |
848 | +class TestBugzilla(BugTrackerResponsesMixin, Bugzilla): |
849 | + """Bugzilla ExternalSystem for use in tests.""" |
850 | # We set the batch_query_threshold to zero so that only |
851 | # getRemoteBugBatch() is used to retrieve bugs, since getRemoteBug() |
852 | # calls getRemoteBugBatch() anyway. |
853 | @@ -301,40 +300,38 @@ |
854 | """ |
855 | return read_test_file(self.bug_item_file) |
856 | |
857 | - def _getPage(self, page): |
858 | - """GET a page. |
859 | + def _getCallback(self, request): |
860 | + """Handle a test GET request. |
861 | |
862 | Only handles xml.cgi?id=1 so far. |
863 | """ |
864 | - if self.trace_calls: |
865 | - print "CALLED _getPage()" |
866 | - if page == 'xml.cgi?id=1': |
867 | - data = read_test_file(self.version_file) |
868 | + url = urlsplit(request.url) |
869 | + if (url.path == urlsplit(self.baseurl).path + '/xml.cgi' and |
870 | + parse_qs(url.query).get('id') == ['1']): |
871 | + body = read_test_file(self.version_file) |
872 | # Add some latin1 to test bug 61129 |
873 | - return data % dict(non_ascii_latin1="\xe9") |
874 | + return 200, {}, body % {'non_ascii_latin1': b'\xe9'} |
875 | else: |
876 | - raise AssertionError('Unknown page: %s' % page) |
877 | - |
878 | - def _postPage(self, page, form, repost_on_redirect=False): |
879 | - """POST to the specified page. |
880 | - |
881 | - :form: is a dict of form variables being POSTed. |
882 | + raise AssertionError('Unknown URL: %s' % request.url) |
883 | + |
884 | + def _postCallback(self, request): |
885 | + """Handle a test POST request. |
886 | |
887 | Only handles buglist.cgi so far. |
888 | """ |
889 | - if self.trace_calls: |
890 | - print "CALLED _postPage()" |
891 | - if page == self.buglist_page: |
892 | + url = urlsplit(request.url) |
893 | + if url.path == urlsplit(self.baseurl).path + '/' + self.buglist_page: |
894 | buglist_xml = read_test_file(self.buglist_file) |
895 | - bug_ids = str(form[self.bug_id_form_element]).split(',') |
896 | + form = parse_qs(request.body) |
897 | + bug_ids = str(form[self.bug_id_form_element][0]).split(',') |
898 | bug_li_items = [] |
899 | for bug_id in bug_ids: |
900 | bug_id = int(bug_id) |
901 | if bug_id not in self.bugzilla_bugs: |
902 | - #Unknown bugs aren't included in the resulting xml. |
903 | + # Unknown bugs aren't included in the resulting xml. |
904 | continue |
905 | - bug_status, bug_resolution, bug_priority, bug_severity = \ |
906 | - self.bugzilla_bugs[int(bug_id)] |
907 | + bug_status, bug_resolution, bug_priority, bug_severity = ( |
908 | + self.bugzilla_bugs[int(bug_id)]) |
909 | bug_item = self._readBugItemFile() % { |
910 | 'bug_id': bug_id, |
911 | 'status': bug_status, |
912 | @@ -343,12 +340,22 @@ |
913 | 'severity': bug_severity, |
914 | } |
915 | bug_li_items.append(bug_item) |
916 | - return buglist_xml % { |
917 | + body = buglist_xml % { |
918 | 'bug_li_items': '\n'.join(bug_li_items), |
919 | - 'page': page, |
920 | + 'page': url.path.lstrip('/'), |
921 | } |
922 | + return 200, {}, body |
923 | else: |
924 | - raise AssertionError('Unknown page: %s' % page) |
925 | + raise AssertionError('Unknown URL: %s' % request.url) |
926 | + |
927 | + def addResponses(self, requests_mock, get=True, post=True): |
928 | + """Add test responses.""" |
929 | + if get: |
930 | + requests_mock.add_callback( |
931 | + 'GET', re.compile(r'.*'), self._getCallback) |
932 | + if post: |
933 | + requests_mock.add_callback( |
934 | + 'POST', re.compile(r'.*'), self._postCallback) |
935 | |
936 | |
937 | class TestWeirdBugzilla(TestBugzilla): |
938 | @@ -406,14 +413,7 @@ |
939 | 123543: ('ASSIGNED', '', 'HIGH', 'BLOCKER')} |
940 | |
941 | |
942 | -class FakeHTTPConnection: |
943 | - """A fake HTTP connection.""" |
944 | - |
945 | - def putheader(self, header, value): |
946 | - print "%s: %s" % (header, value) |
947 | - |
948 | - |
949 | -class TestBugzillaXMLRPCTransport(UrlLib2Transport): |
950 | +class TestBugzillaXMLRPCTransport(RequestsTransport): |
951 | """A test implementation of the Bugzilla XML-RPC interface.""" |
952 | |
953 | local_datetime = None |
954 | @@ -538,9 +538,9 @@ |
955 | |
956 | def __init__(self, *args, **kwargs): |
957 | """Ensure mutable class data is copied to the instance.""" |
958 | - # UrlLib2Transport is not a new style class so 'super' cannot be |
959 | + # RequestsTransport is not a new-style class so 'super' cannot be |
960 | # used. |
961 | - UrlLib2Transport.__init__(self, *args, **kwargs) |
962 | + RequestsTransport.__init__(self, *args, **kwargs) |
963 | self.bugs = deepcopy(TestBugzillaXMLRPCTransport._bugs) |
964 | self.bug_aliases = deepcopy(self._bug_aliases) |
965 | self.bug_comments = deepcopy(self._bug_comments) |
966 | @@ -551,14 +551,13 @@ |
967 | |
968 | @property |
969 | def auth_cookie(self): |
970 | - cookies = self.cookie_processor.cookiejar._cookies |
971 | - |
972 | - assert len(cookies) < 2, ( |
973 | - "There should only be cookies for one domain.") |
974 | - |
975 | - if len(cookies) == 1: |
976 | - [(domain, domain_cookies)] = cookies.items() |
977 | - return domain_cookies.get('', {}).get('Bugzilla_logincookie') |
978 | + if len(set(cookie.domain for cookie in self.cookie_jar)) > 1: |
979 | + raise AssertionError( |
980 | + "There should only be cookies for one domain.") |
981 | + |
982 | + for cookie in self.cookie_jar: |
983 | + if cookie.name == 'Bugzilla_logincookie': |
984 | + return cookie |
985 | else: |
986 | return None |
987 |