~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

__metaclass__ = type

from storm.expr import LeftJoin
from storm.store import Store
from testtools.matchers import Equals
from zope.component import getUtility

from canonical.launchpad.testing.pages import (
    extract_text,
    find_tag_by_id,
    find_tags_by_class,
    )
from canonical.launchpad.webapp.publisher import canonical_url
from canonical.testing.layers import DatabaseFunctionalLayer
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.bugs.model.bugtask import BugTask
from lp.registry.model.person import Person
from lp.testing import (
    BrowserTestCase,
    login_person,
    person_logged_in,
    StormStatementRecorder,
    TestCaseWithFactory,
    )
from lp.testing.matchers import HasQueryCount
from lp.testing.views import create_initialized_view


class TestBugTaskSearchListingPage(BrowserTestCase):

    layer = DatabaseFunctionalLayer

    def _makeDistributionSourcePackage(self):
        distro = self.factory.makeDistribution('test-distro')
        return self.factory.makeDistributionSourcePackage('test-dsp', distro)

    def test_distributionsourcepackage_unknown_bugtracker_message(self):
        # A DistributionSourcePackage whose Distro does not use
        # Launchpad for bug tracking should explain that.
        dsp = self._makeDistributionSourcePackage()
        url = canonical_url(dsp, rootsite='bugs')
        browser = self.getUserBrowser(url)
        top_portlet = find_tags_by_class(
            browser.contents, 'top-portlet')
        self.assertTrue(len(top_portlet) > 0,
                        "Tag with class=top-portlet not found")
        self.assertTextMatchesExpressionIgnoreWhitespace("""
            test-dsp in Test-distro does not use Launchpad for bug tracking.
            Getting started with bug tracking in Launchpad.""",
            extract_text(top_portlet[0]))

    def test_distributionsourcepackage_unknown_bugtracker_no_button(self):
        # A DistributionSourcePackage whose Distro does not use
        # Launchpad for bug tracking should not show the "Report a bug"
        # button.
        dsp = self._makeDistributionSourcePackage()
        url = canonical_url(dsp, rootsite='bugs')
        browser = self.getUserBrowser(url)
        self.assertIs(None, find_tag_by_id(browser.contents, 'involvement'),
                      "Involvement portlet with Report-a-bug button should "
                      "not be shown")

    def test_distributionsourcepackage_unknown_bugtracker_no_filters(self):
        # A DistributionSourcePackage whose Distro does not use
        # Launchpad for bug tracking should not show links to "New
        # bugs", "Open bugs", etc.
        dsp = self._makeDistributionSourcePackage()
        url = canonical_url(dsp, rootsite='bugs')
        browser = self.getUserBrowser(url)
        self.assertIs(None,
                      find_tag_by_id(browser.contents, 'portlet-bugfilters'),
                      "portlet-bugfilters should not be shown.")

    def test_distributionsourcepackage_unknown_bugtracker_no_tags(self):
        # A DistributionSourcePackage whose Distro does not use
        # Launchpad for bug tracking should not show links to search by
        # bug tags.
        dsp = self._makeDistributionSourcePackage()
        url = canonical_url(dsp, rootsite='bugs')
        browser = self.getUserBrowser(url)
        self.assertIs(None, find_tag_by_id(browser.contents, 'portlet-tags'),
                      "portlet-tags should not be shown.")

    def _makeSourcePackage(self):
        distro = self.factory.makeDistribution('test-distro')
        self.factory.makeDistroSeries(distribution=distro, name='test-series')
        return self.factory.makeSourcePackage('test-sp', distro.currentseries)

    def test_sourcepackage_unknown_bugtracker_message(self):
        # A SourcePackage whose Distro does not use
        # Launchpad for bug tracking should explain that.
        sp = self._makeSourcePackage()
        url = canonical_url(sp, rootsite='bugs')
        browser = self.getUserBrowser(url)
        top_portlet = find_tags_by_class(
            browser.contents, 'top-portlet')
        self.assertTrue(len(top_portlet) > 0,
                        "Tag with class=top-portlet not found")
        self.assertTextMatchesExpressionIgnoreWhitespace("""
            test-sp in Test-distro Test-series does not
            use Launchpad for bug tracking.
            Getting started with bug tracking in Launchpad.""",
            extract_text(top_portlet[0]))

    def test_sourcepackage_unknown_bugtracker_no_button(self):
        # A SourcePackage whose Distro does not use Launchpad for bug
        # tracking should not show the "Report a bug" button.
        sp = self._makeSourcePackage()
        url = canonical_url(sp, rootsite='bugs')
        browser = self.getUserBrowser(url)
        self.assertIs(None, find_tag_by_id(browser.contents, 'involvement'),
                      "Involvement portlet with Report-a-bug button should "
                      "not be shown")

    def test_sourcepackage_unknown_bugtracker_no_filters(self):
        # A SourcePackage whose Distro does not use Launchpad for bug
        # tracking should not show links to "New bugs", "Open bugs",
        # etc.
        sp = self._makeSourcePackage()
        url = canonical_url(sp, rootsite='bugs')
        browser = self.getUserBrowser(url)
        self.assertIs(None,
                      find_tag_by_id(browser.contents, 'portlet-bugfilters'),
                      "portlet-bugfilters should not be shown.")

    def test_sourcepackage_unknown_bugtracker_no_tags(self):
        # A SourcePackage whose Distro does not use Launchpad for bug
        # tracking should not show links to search by bug tags.
        sp = self._makeSourcePackage()
        url = canonical_url(sp, rootsite='bugs')
        browser = self.getUserBrowser(url)
        self.assertIs(None,
                      find_tag_by_id(browser.contents, 'portlet-tags'),
                      "portlet-tags should not be shown.")

    def test_searchUnbatched_can_preload_objects(self):
        # BugTaskSearchListingView.searchUnbatched() can optionally
        # preload objects while retrieving the bugtasks.
        product = self.factory.makeProduct()
        bugtask_1 = self.factory.makeBug(product=product).default_bugtask
        bugtask_2 = self.factory.makeBug(product=product).default_bugtask
        view = create_initialized_view(product, '+bugs')
        Store.of(product).invalidate()
        with StormStatementRecorder() as recorder:
            prejoins = [
                (Person, LeftJoin(Person, BugTask.owner == Person.id)),
                ]
            bugtasks = list(view.searchUnbatched(prejoins=prejoins))
            self.assertEqual(
                [bugtask_1, bugtask_2], bugtasks)
            # If the table prejoin failed, then this will issue two
            # additional SQL queries
            [bugtask.owner for bugtask in bugtasks]
        self.assertThat(recorder, HasQueryCount(Equals(2)))

    def test_search_components_error(self):
        # Searching for using components for bug targets that are not a distro
        # or distroseries will report an error, but not OOPS.  See bug
        # 838957.
        product = self.factory.makeProduct()
        form = {
            'search': 'Search',
            'advanced': 1,
            'field.component': 1,
            'field.component-empty-marker': 1}
        with person_logged_in(product.owner):
            view = create_initialized_view(product, '+bugs', form=form)
            view.searchUnbatched()
        response = view.request.response
        self.assertEqual(1, len(response.notifications))
        expected = (
            "Search by component requires a context with "
            "a distribution or distroseries.")
        self.assertEqual(expected, response.notifications[0].message)
        self.assertEqual(
            canonical_url(product, rootsite='bugs', view_name='+bugs'),
            response.getHeader('Location'))


class BugTargetTestCase(TestCaseWithFactory):
    """Test helpers for setting up `IBugTarget` tests."""

    def _makeBugTargetProduct(self, bug_tracker=None, packaging=False):
        """Return a product that may use Launchpad or an external bug tracker.

        bug_tracker may be None, 'launchpad', or 'external'.
        """
        product = self.factory.makeProduct()
        if bug_tracker is not None:
            with person_logged_in(product.owner):
                if bug_tracker == 'launchpad':
                    product.official_malone = True
                else:
                    product.bugtracker = self.factory.makeBugTracker()
        if packaging:
            self.factory.makePackagingLink(
                productseries=product.development_focus, in_ubuntu=True)
        return product


class TestBugTaskSearchListingViewProduct(BugTargetTestCase):

    layer = DatabaseFunctionalLayer

    def test_external_bugtracker_is_none(self):
        bug_target = self._makeBugTargetProduct()
        view = create_initialized_view(bug_target, '+bugs')
        self.assertEqual(None, view.external_bugtracker)

    def test_external_bugtracker(self):
        bug_target = self._makeBugTargetProduct(bug_tracker='external')
        view = create_initialized_view(bug_target, '+bugs')
        self.assertEqual(bug_target.bugtracker, view.external_bugtracker)

    def test_has_bugtracker_is_false(self):
        bug_target = self.factory.makeProduct()
        view = create_initialized_view(bug_target, '+bugs')
        self.assertEqual(False, view.has_bugtracker)

    def test_has_bugtracker_external_is_true(self):
        bug_target = self._makeBugTargetProduct(bug_tracker='external')
        view = create_initialized_view(bug_target, '+bugs')
        self.assertEqual(True, view.has_bugtracker)

    def test_has_bugtracker_launchpad_is_true(self):
        bug_target = self._makeBugTargetProduct(bug_tracker='launchpad')
        view = create_initialized_view(bug_target, '+bugs')
        self.assertEqual(True, view.has_bugtracker)

    def test_product_without_packaging_also_in_ubuntu_is_none(self):
        bug_target = self._makeBugTargetProduct(bug_tracker='launchpad')
        login_person(bug_target.owner)
        view = create_initialized_view(
            bug_target, '+bugs', principal=bug_target.owner)
        self.assertEqual(None, find_tag_by_id(view(), 'also-in-ubuntu'))

    def test_product_with_packaging_also_in_ubuntu(self):
        bug_target = self._makeBugTargetProduct(
            bug_tracker='launchpad', packaging=True)
        login_person(bug_target.owner)
        view = create_initialized_view(
            bug_target, '+bugs', principal=bug_target.owner)
        content = find_tag_by_id(view.render(), 'also-in-ubuntu')
        link = canonical_url(
            bug_target.ubuntu_packages[0], force_local_path=True)
        self.assertEqual(link, content.a['href'])

    def test_ask_question_does_not_use_launchpad(self):
        bug_target = self._makeBugTargetProduct(
            bug_tracker='launchpad', packaging=True)
        login_person(bug_target.owner)
        bug_target.official_answers = False
        view = create_initialized_view(
            bug_target, '+bugs', principal=bug_target.owner)
        self.assertEqual(None, view.addquestion_url)

    def test_ask_question_uses_launchpad(self):
        bug_target = self._makeBugTargetProduct(
            bug_tracker='launchpad', packaging=True)
        login_person(bug_target.owner)
        bug_target.official_answers = True
        view = create_initialized_view(
            bug_target, '+bugs', principal=bug_target.owner)
        url = canonical_url(
            bug_target, rootsite='answers', view_name='+addquestion')
        self.assertEqual(url, view.addquestion_url)


class TestBugTaskSearchListingViewDSP(BugTargetTestCase):

    layer = DatabaseFunctionalLayer

    def _getBugTarget(self, obj):
        """Return the `IBugTarget` under test.

        Return the object that was passed. Sub-classes can redefine
        this method.
        """
        return obj

    def test_package_with_upstream_launchpad_project(self):
        upstream_project = self._makeBugTargetProduct(
            bug_tracker='launchpad', packaging=True)
        login_person(upstream_project.owner)
        bug_target = self._getBugTarget(
            upstream_project.distrosourcepackages[0])
        view = create_initialized_view(
            bug_target, '+bugs', principal=upstream_project.owner)
        self.assertEqual(upstream_project, view.upstream_launchpad_project)
        content = find_tag_by_id(view.render(), 'also-in-upstream')
        link = canonical_url(upstream_project, rootsite='bugs')
        self.assertEqual(link, content.a['href'])

    def test_package_with_upstream_nonlaunchpad_project(self):
        upstream_project = self._makeBugTargetProduct(packaging=True)
        login_person(upstream_project.owner)
        bug_target = self._getBugTarget(
            upstream_project.distrosourcepackages[0])
        view = create_initialized_view(
            bug_target, '+bugs', principal=upstream_project.owner)
        self.assertEqual(None, view.upstream_launchpad_project)
        self.assertEqual(None, find_tag_by_id(view(), 'also-in-upstream'))

    def test_package_without_upstream_project(self):
        observer = self.factory.makePerson()
        dsp = self.factory.makeDistributionSourcePackage(
            'test-dsp', distribution=getUtility(ILaunchpadCelebrities).ubuntu)
        bug_target = self._getBugTarget(dsp)
        login_person(observer)
        view = create_initialized_view(
            bug_target, '+bugs', principal=observer)
        self.assertEqual(None, view.upstream_launchpad_project)
        self.assertEqual(None, find_tag_by_id(view(), 'also-in-upstream'))


class TestBugTaskSearchListingViewSP(TestBugTaskSearchListingViewDSP):

        def _getBugTarget(self, dsp):
            """Return the current `ISourcePackage` for the dsp."""
            return dsp.development_version