Merge lp:~sinzui/launchpad/incomplete-api into lp:launchpad

Proposed by Curtis Hovey on 2012-09-20
Status: Merged
Approved by: j.c.sackett on 2012-09-20
Approved revision: no longer in the source branch.
Merged at revision: 16000
Proposed branch: lp:~sinzui/launchpad/incomplete-api
Merge into: lp:launchpad
Diff against target: 111 lines (+42/-5)
3 files modified
lib/lp/bugs/model/bugtasksearch.py (+11/-5)
lib/lp/bugs/model/tests/test_bugtasksearch.py (+16/-0)
lib/lp/bugs/tests/test_searchtasks_webservice.py (+15/-0)
To merge this branch: bzr merge lp:~sinzui/launchpad/incomplete-api
Reviewer Review Type Date Requested Status
j.c.sackett (community) 2012-09-20 Approve on 2012-09-20
Review via email: mp+125515@code.launchpad.net

Commit Message

Restore API support for searching for Incomplete bug tasks.

Description of the Change

This is possibly a regression from the change to store the actual distinct
incomplete enum in the DB.

--------------------------------------------------------------------

RULES

    Pre-implementation: no one
    * Create an API test, verify it fails, then use the debugger to
      find where INCOMPLETE fails to become the other two completes.
    * Ah, the _build_status_clause() does the adaption, but does not
      because it is checking for BugTaskStatus.INCOMPLETE, but the
      API sends BugTaskStatusSearch.INCOMPLETE.
      * Both internal and external search use BugTaskStatusSearch, but our
        view know that the model uses BugTaskStatus so it is adapted
        early before going to the model. API goes directly to the model
        and is not adapted, _build_status_clause() needs to know about both.

QA

    * Run this script and verify more that 20 bugs are found:

    {{{
    from launchpadlib.launchpad import Launchpad
    lp = Launchpad.login_with(
        'testing', service_root='https://api.qastaging.launchpad.net',
        version='devel')
    project = lp.projects['launchpad']
    tasks = project.searchTasks(status="Incomplete")
    for task in tasks:
        print task.title
    print '%d bug tasks found' % len(tasks)
    }}}

LINT

    lib/lp/bugs/model/bugtasksearch.py
    lib/lp/bugs/model/tests/test_bugtasksearch.py
    lib/lp/bugs/tests/test_searchtasks_webservice.py

LoC

    I have a 3000+ line credit as of this week.

TEST

    ./bin/test -vvc lp.bugs.tests.test_searchtasks_webservice
    ./bin/test -vvc -t SetStatusSearch lp.bugs.model.tests.test_bugtasksearch

IMPLEMENTATION

I updated _build_status_clause() to make the API integration test pass, but
then saw the method handles single and list values. I found the tests for
the method and add two new tests based on the existing INCOMPLETE tests
to verify both enums are supported.
    lib/lp/bugs/model/bugtasksearch.py
    lib/lp/bugs/model/tests/test_bugtasksearch.py
    lib/lp/bugs/tests/test_searchtasks_webservice.py

To post a comment you must log in.
j.c.sackett (jcsackett) wrote :

Tricky bug, good fix. Thanks Curtis.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
2--- lib/lp/bugs/model/bugtasksearch.py 2012-09-19 01:19:35 +0000
3+++ lib/lp/bugs/model/bugtasksearch.py 2012-09-21 02:06:18 +0000
4@@ -894,13 +894,16 @@
5 """Return the SQL query fragment for search by status.
6
7 Called from `_build_query` or recursively."""
8+
9 if zope_isinstance(status, any):
10 values = list(status.query_values)
11- # Since INCOMPLETE isn't stored as a single value we need to
12+ # Since INCOMPLETE isn't stored as a single value any more, we need to
13 # expand it before generating the SQL.
14- if BugTaskStatus.INCOMPLETE in values:
15- values.remove(BugTaskStatus.INCOMPLETE)
16- values.extend(DB_INCOMPLETE_BUGTASK_STATUSES)
17+ old = set([BugTaskStatus.INCOMPLETE, BugTaskStatusSearch.INCOMPLETE])
18+ accepted_values = list(set(values) - old)
19+ if len(accepted_values) < len(values):
20+ accepted_values.extend(DB_INCOMPLETE_BUGTASK_STATUSES)
21+ values = accepted_values
22 return search_value_to_storm_where_condition(col, any(*values))
23 elif zope_isinstance(status, not_equals):
24 return Not(_build_status_clause(col, status.value))
25@@ -908,7 +911,10 @@
26 # INCOMPLETE is not stored in the DB, instead one of
27 # DB_INCOMPLETE_BUGTASK_STATUSES is stored, so any request to
28 # search for INCOMPLETE should instead search for those values.
29- if status == BugTaskStatus.INCOMPLETE:
30+ # BugTaskStatus is used internally, BugTaskStatusSearch is used
31+ # externally, such as API.
32+ if (status == BugTaskStatus.INCOMPLETE
33+ or status == BugTaskStatusSearch.INCOMPLETE):
34 status = any(*DB_INCOMPLETE_BUGTASK_STATUSES)
35 return search_value_to_storm_where_condition(col, status)
36 else:
37
38=== modified file 'lib/lp/bugs/model/tests/test_bugtasksearch.py'
39--- lib/lp/bugs/model/tests/test_bugtasksearch.py 2012-09-18 18:36:09 +0000
40+++ lib/lp/bugs/model/tests/test_bugtasksearch.py 2012-09-21 02:06:18 +0000
41@@ -26,6 +26,7 @@
42 from lp.bugs.interfaces.bugtask import (
43 BugTaskImportance,
44 BugTaskStatus,
45+ BugTaskStatusSearch,
46 IBugTaskSet,
47 )
48 from lp.bugs.interfaces.bugtasksearch import (
49@@ -1771,6 +1772,13 @@
50 'BugTask.status IN (13, 14)',
51 self.searchClause(BugTaskStatus.INCOMPLETE))
52
53+ def test_BugTaskStatusSearch_INCOMPLETE_query(self):
54+ # BugTaskStatusSearch.INCOMPLETE is treated as
55+ # BugTaskStatus.INCOMPLETE.
56+ self.assertEqual(
57+ 'BugTask.status IN (13, 14)',
58+ self.searchClause(BugTaskStatusSearch.INCOMPLETE))
59+
60 def test_negative_query(self):
61 # If a negative is requested then the WHERE clause is simply wrapped
62 # in a "NOT".
63@@ -1799,6 +1807,14 @@
64 self.searchClause(
65 any(BugTaskStatus.NEW, BugTaskStatus.INCOMPLETE)))
66
67+ def test_any_query_with_BugTaskStatusSearch_INCOMPLETE(self):
68+ # BugTaskStatusSearch.INCOMPLETE is treated as
69+ # BugTaskStatus.INCOMPLETE.
70+ self.assertEqual(
71+ 'BugTask.status IN (10, 13, 14)',
72+ self.searchClause(
73+ any(BugTaskStatus.NEW, BugTaskStatusSearch.INCOMPLETE)))
74+
75 def test_all_query(self):
76 # Since status is single-valued, asking for "all" statuses in a set
77 # doesn't make any sense.
78
79=== modified file 'lib/lp/bugs/tests/test_searchtasks_webservice.py'
80--- lib/lp/bugs/tests/test_searchtasks_webservice.py 2012-09-18 18:36:09 +0000
81+++ lib/lp/bugs/tests/test_searchtasks_webservice.py 2012-09-21 02:06:18 +0000
82@@ -5,7 +5,9 @@
83
84 __metaclass__ = type
85
86+
87 from lp.app.enums import InformationType
88+from lp.bugs.interfaces.bugtask import BugTaskStatusSearch
89 from lp.testing import (
90 person_logged_in,
91 TestCaseWithFactory,
92@@ -107,6 +109,19 @@
93 ValueError, "Unrecognized order_by: u'date_created'",
94 response.jsonBody)
95
96+ def test_search_incomplete_status_results(self):
97+ # The Incomplete status matches Incomplete with response and
98+ # Incomplete without response bug tasks.
99+ with person_logged_in(self.owner):
100+ self.factory.makeBug(
101+ target=self.product,
102+ status=BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE)
103+ self.factory.makeBug(
104+ target=self.product,
105+ status=BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE)
106+ response = self.search("devel", status="Incomplete")
107+ self.assertEqual(response['total_size'], 2)
108+
109
110 class TestGetBugData(TestCaseWithFactory):
111 """Tests for the /bugs getBugData operation."""