Merge lp:~stevenk/launchpad/proper-error-when-searchtasks-orderby into lp:launchpad

Proposed by Steve Kowalik on 2012-09-17
Status: Merged
Approved by: William Grant on 2012-09-17
Approved revision: no longer in the source branch.
Merged at revision: 15970
Proposed branch: lp:~stevenk/launchpad/proper-error-when-searchtasks-orderby
Merge into: lp:launchpad
Diff against target: 192 lines (+41/-36)
4 files modified
lib/lp/bugs/errors.py (+6/-0)
lib/lp/bugs/model/bugtasksearch.py (+14/-7)
lib/lp/bugs/model/tests/test_bugtasksearch.py (+3/-2)
lib/lp/bugs/tests/test_searchtasks_webservice.py (+18/-27)
To merge this branch: bzr merge lp:~stevenk/launchpad/proper-error-when-searchtasks-orderby
Reviewer Review Type Date Requested Status
William Grant code 2012-09-17 Approve on 2012-09-17
Review via email: mp+124609@code.launchpad.net

Commit Message

Make searchTasks more friendly to the API by returning 400 Bad Request rather than ValueError/KeyError and OOPSing.

Description of the Change

Currently, if you pass the wrong order_by argument to searchTasks, it will raise an OOPS with a KeyError. Slightly restructure the code and make sure to raise a 400 Bad Request if the argument is wrong. I have also changed all occurances of ValueError in bugtasksearch to be more friendly to the API, but have subclassed ValueError so that existing browser code will cope.

To post a comment you must log in.
William Grant (wgrant) wrote :

I'd hope the test could be moved into an existing class, but otherwise good.

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/errors.py'
2--- lib/lp/bugs/errors.py 2012-08-28 03:56:19 +0000
3+++ lib/lp/bugs/errors.py 2012-09-18 04:33:20 +0000
4@@ -6,6 +6,7 @@
5 __metaclass__ = type
6 __all__ = [
7 'InvalidDuplicateValue',
8+ 'InvalidSearchParameters',
9 ]
10
11 import httplib
12@@ -18,3 +19,8 @@
13 @error_status(httplib.EXPECTATION_FAILED)
14 class InvalidDuplicateValue(LaunchpadValidationError):
15 """A bug cannot be set as the duplicate of another."""
16+
17+
18+@error_status(httplib.BAD_REQUEST)
19+class InvalidSearchParameters(ValueError):
20+ """Invalid search parameters were passed to searchTasks."""
21
22=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
23--- lib/lp/bugs/model/bugtasksearch.py 2012-08-08 05:43:18 +0000
24+++ lib/lp/bugs/model/bugtasksearch.py 2012-09-18 04:33:20 +0000
25@@ -41,6 +41,7 @@
26 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
27 from lp.blueprints.model.specification import Specification
28 from lp.blueprints.model.specificationbug import SpecificationBug
29+from lp.bugs.errors import InvalidSearchParameters
30 from lp.bugs.interfaces.bugattachment import BugAttachmentType
31 from lp.bugs.interfaces.bugnomination import BugNominationStatus
32 from lp.bugs.interfaces.bugtask import (
33@@ -383,7 +384,7 @@
34 'Excluding conjoined tasks is not currently supported '
35 'for milestone tags')
36 if not params.milestone:
37- raise ValueError(
38+ raise InvalidSearchParameters(
39 "BugTaskSearchParam.exclude_conjoined cannot be True if "
40 "BugTaskSearchParam.milestone is not set")
41
42@@ -578,7 +579,7 @@
43 elif params.distroseries:
44 distroseries = params.distroseries
45 if distroseries is None:
46- raise ValueError(
47+ raise InvalidSearchParameters(
48 "Search by component requires a context with a "
49 "distribution or distroseries.")
50
51@@ -827,13 +828,18 @@
52 if isinstance(orderby_col, SQLConstant):
53 orderby_arg.append(orderby_col)
54 continue
55+ desc_search = False
56 if orderby_col.startswith("-"):
57- col, sort_joins = orderby_expression[orderby_col[1:]]
58- extra_joins.extend(sort_joins)
59+ orderby_col = orderby_col[1:]
60+ desc_search = True
61+ if orderby_col not in orderby_expression:
62+ raise InvalidSearchParameters(
63+ "Unrecognized order_by: %r" % (orderby_col,))
64+ col, sort_joins = orderby_expression[orderby_col]
65+ extra_joins.extend(sort_joins)
66+ if desc_search:
67 order_clause = Desc(col)
68 else:
69- col, sort_joins = orderby_expression[orderby_col]
70- extra_joins.extend(sort_joins)
71 order_clause = col
72 if col in unambiguous_cols:
73 ambiguous = False
74@@ -906,7 +912,8 @@
75 status = any(*DB_INCOMPLETE_BUGTASK_STATUSES)
76 return search_value_to_storm_where_condition(col, status)
77 else:
78- raise ValueError('Unrecognized status value: %r' % (status,))
79+ raise InvalidSearchParameters(
80+ 'Unrecognized status value: %r' % (status,))
81
82
83 def _build_exclude_conjoined_clause(milestone):
84
85=== modified file 'lib/lp/bugs/model/tests/test_bugtasksearch.py'
86--- lib/lp/bugs/model/tests/test_bugtasksearch.py 2012-08-24 05:09:51 +0000
87+++ lib/lp/bugs/model/tests/test_bugtasksearch.py 2012-09-18 04:33:20 +0000
88@@ -19,6 +19,7 @@
89
90 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
91 from lp.app.interfaces.services import IService
92+from lp.bugs.errors import InvalidSearchParameters
93 from lp.bugs.interfaces.bugattachment import BugAttachmentType
94 from lp.bugs.interfaces.bugtarget import IBugTarget
95 from lp.bugs.interfaces.bugtask import (
96@@ -1803,13 +1804,13 @@
97 def test_all_query(self):
98 # Since status is single-valued, asking for "all" statuses in a set
99 # doesn't make any sense.
100- with ExpectedException(ValueError):
101+ with ExpectedException(InvalidSearchParameters):
102 self.searchClause(
103 all(BugTaskStatus.NEW, BugTaskStatus.INCOMPLETE))
104
105 def test_bad_value(self):
106 # If an unrecognized status is provided then an error is raised.
107- with ExpectedException(ValueError):
108+ with ExpectedException(InvalidSearchParameters):
109 self.searchClause('this-is-not-a-status')
110
111
112
113=== modified file 'lib/lp/bugs/tests/test_searchtasks_webservice.py'
114--- lib/lp/bugs/tests/test_searchtasks_webservice.py 2012-08-08 11:48:29 +0000
115+++ lib/lp/bugs/tests/test_searchtasks_webservice.py 2012-09-18 04:33:20 +0000
116@@ -1,4 +1,4 @@
117-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
118+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
119 # GNU Affero General Public License version 3 (see the file LICENSE).
120
121 """Webservice unit tests related to Launchpad Bugs."""
122@@ -41,17 +41,20 @@
123 self.assertEqual(response['total_size'], 1)
124
125
126-class TestLinkedBlueprintsParameter(TestCaseWithFactory):
127- """Tests for the linked_blueprints parameter."""
128+class TestProductSearchTasks(TestCaseWithFactory):
129+ """Tests for the information_type, linked_blueprints and order_by
130+ parameters."""
131
132 layer = DatabaseFunctionalLayer
133
134 def setUp(self):
135- super(TestLinkedBlueprintsParameter, self).setUp()
136+ super(TestProductSearchTasks, self).setUp()
137 self.owner = self.factory.makePerson()
138 with person_logged_in(self.owner):
139 self.product = self.factory.makeProduct()
140- self.bug = self.factory.makeBugTask(target=self.product)
141+ self.bug = self.factory.makeBug(
142+ target=self.product,
143+ information_type=InformationType.PRIVATESECURITY)
144 self.webservice = LaunchpadWebServiceCaller(
145 'launchpad-library', 'salgado-change-anything')
146
147@@ -83,28 +86,6 @@
148 # thus no error is returned when we pass rubbish.
149 self.search("beta", linked_blueprints="Teabags!")
150
151-
152-class TestSearchByInformationType(TestCaseWithFactory):
153- """Tests for the information_type parameter."""
154-
155- layer = DatabaseFunctionalLayer
156-
157- def setUp(self):
158- super(TestSearchByInformationType, self).setUp()
159- self.owner = self.factory.makePerson()
160- with person_logged_in(self.owner):
161- self.product = self.factory.makeProduct()
162- self.bug = self.factory.makeBug(
163- target=self.product,
164- information_type=InformationType.PRIVATESECURITY)
165- self.webservice = LaunchpadWebServiceCaller(
166- 'launchpad-library', 'salgado-change-anything')
167-
168- def search(self, api_version, **kwargs):
169- return self.webservice.named_get(
170- '/%s' % self.product.name, 'searchTasks',
171- api_version=api_version, **kwargs).jsonBody()
172-
173 def test_search_returns_results(self):
174 # A matching search returns results.
175 response = self.search(
176@@ -116,6 +97,16 @@
177 response = self.search("devel", information_type="Private")
178 self.assertEqual(response['total_size'], 0)
179
180+ def test_search_with_wrong_orderby(self):
181+ # Calling searchTasks() with a wrong order_by is a Bad Request.
182+ response = self.webservice.named_get(
183+ '/%s' % self.product.name, 'searchTasks',
184+ api_version='devel', order_by='date_created')
185+ self.assertEqual(400, response.status)
186+ self.assertRaisesWithContent(
187+ ValueError, "Unrecognized order_by: u'date_created'",
188+ response.jsonBody)
189+
190
191 class TestGetBugData(TestCaseWithFactory):
192 """Tests for the /bugs getBugData operation."""