~launchpad-pqm/launchpad/devel

11745.1.3 by Edwin Grubbs
Fixed lint errors.
1
# Copyright 2010 Canonical Ltd.  This software is licensed under the
11745.1.2 by Edwin Grubbs
Fixed extra_clause placement in query.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
4
"""Test the person vocabularies."""
5
6
__metaclass__ = type
7
8
from storm.store import Store
13222.1.1 by William Grant
Test that ValidPersonOrTeamVocabulary's preloading actually works.
9
from testtools.matchers import Equals
13171.2.5 by William Grant
Let ValidPersonOrTeamVocabulary order by project affiliation (behind a separate flag).
10
from zope.component import getUtility
11745.1.2 by Edwin Grubbs
Fixed extra_clause placement in query.
11
from zope.schema.vocabulary import getVocabularyRegistry
11954.6.8 by Curtis Hovey
Removed undeeded vocabulary and added a test for displayname.
12
from zope.security.proxy import removeSecurityProxy
11745.1.2 by Edwin Grubbs
Fixed extra_clause placement in query.
13
13222.1.1 by William Grant
Test that ValidPersonOrTeamVocabulary's preloading actually works.
14
from lp.registry.interfaces.irc import IIrcIDSet
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
15
from lp.registry.interfaces.karma import IKarmaCacheManager
11954.6.5 by Curtis Hovey
Added ValidPersonOrClosedTeamVocabulary.
16
from lp.registry.interfaces.person import (
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
17
    CLOSED_TEAM_POLICY,
18
    OPEN_TEAM_POLICY,
11954.6.5 by Curtis Hovey
Added ValidPersonOrClosedTeamVocabulary.
19
    PersonVisibility,
20
    TeamSubscriptionPolicy,
14235.2.7 by Ian Booth
More lint
21
    )
13746.3.1 by Ian Booth
Add filtering to person related vocabs
22
from lp.registry.vocabularies import ValidPersonOrTeamVocabulary
14612.2.1 by William Grant
format-imports on lib/. So many imports.
23
from lp.services.webapp.vocabulary import FilteredVocabularyBase
13222.1.1 by William Grant
Test that ValidPersonOrTeamVocabulary's preloading actually works.
24
from lp.testing import (
14612.2.1 by William Grant
format-imports on lib/. So many imports.
25
    login_person,
13222.1.1 by William Grant
Test that ValidPersonOrTeamVocabulary's preloading actually works.
26
    StormStatementRecorder,
27
    TestCaseWithFactory,
28
    )
13171.2.5 by William Grant
Let ValidPersonOrTeamVocabulary order by project affiliation (behind a separate flag).
29
from lp.testing.dbuser import dbuser
14612.2.1 by William Grant
format-imports on lib/. So many imports.
30
from lp.testing.layers import (
31
    DatabaseFunctionalLayer,
32
    LaunchpadZopelessLayer,
33
    )
13222.1.1 by William Grant
Test that ValidPersonOrTeamVocabulary's preloading actually works.
34
from lp.testing.matchers import HasQueryCount
13171.2.5 by William Grant
Let ValidPersonOrTeamVocabulary order by project affiliation (behind a separate flag).
35
36
11954.6.11 by Curtis Hovey
Updated ValidTeamMemberVocabulary to prevent open teams from owning closed teams.
37
class VocabularyTestBase:
11954.6.6 by Curtis Hovey
Refactored test so that the fixture can be reused.
38
39
    vocabulary_name = None
40
41
    def setUp(self):
11954.6.11 by Curtis Hovey
Updated ValidTeamMemberVocabulary to prevent open teams from owning closed teams.
42
        super(VocabularyTestBase, self).setUp()
11954.6.6 by Curtis Hovey
Refactored test so that the fixture can be reused.
43
        self.vocabulary_registry = getVocabularyRegistry()
44
11954.6.9 by Curtis Hovey
Removed duplication in test.
45
    def getVocabulary(self, context):
46
        return self.vocabulary_registry.get(context, self.vocabulary_name)
47
13746.3.1 by Ian Booth
Add filtering to person related vocabs
48
    def searchVocabulary(self, context, text, vocab_filter=None):
13171.2.5 by William Grant
Let ValidPersonOrTeamVocabulary order by project affiliation (behind a separate flag).
49
        if Store.of(context) is not None:
50
            Store.of(context).flush()
11954.6.9 by Curtis Hovey
Removed duplication in test.
51
        vocabulary = self.getVocabulary(context)
13746.3.1 by Ian Booth
Add filtering to person related vocabs
52
        removeSecurityProxy(vocabulary).allow_null_search = True
53
        return removeSecurityProxy(vocabulary).search(text, vocab_filter)
11954.6.6 by Curtis Hovey
Refactored test so that the fixture can be reused.
54
13171.2.3 by William Grant
Make test_person_vocabularies.VocabularyTestBase more generic.
55
14235.2.3 by Ian Booth
Add new ValidPillarOwner vocab for pillar owners and security contacts
56
class ValidPersonOrTeamVocabularyMixin(VocabularyTestBase):
57
    """Common tests for the ValidPersonOrTeam vocabulary derivatives."""
13171.2.5 by William Grant
Let ValidPersonOrTeamVocabulary order by project affiliation (behind a separate flag).
58
13746.3.1 by Ian Booth
Add filtering to person related vocabs
59
    def test_supported_filters(self):
60
        # The vocab supports the correct filters.
61
        self.assertEqual([
62
            FilteredVocabularyBase.ALL_FILTER,
63
            ValidPersonOrTeamVocabulary.PERSON_FILTER,
64
            ValidPersonOrTeamVocabulary.TEAM_FILTER,
65
            ],
66
            self.getVocabulary(None).supportedFilters()
67
        )
68
13171.2.5 by William Grant
Let ValidPersonOrTeamVocabulary order by project affiliation (behind a separate flag).
69
    def addKarma(self, person, value, product=None, distribution=None):
70
        if product:
71
            kwargs = dict(product_id=product.id)
72
        elif distribution:
73
            kwargs = dict(distribution_id=distribution.id)
74
        with dbuser('karma'):
75
            getUtility(IKarmaCacheManager).new(
76
                value, person.id, None, **kwargs)
77
78
    def test_people_with_karma_sort_higher(self):
79
        exact_person = self.factory.makePerson(
80
            name='fooix', displayname='Fooix Bar')
81
        prefix_person = self.factory.makePerson(
82
            name='fooix-bar', displayname='Fooix Bar')
83
        contributor_person = self.factory.makePerson(
84
            name='bar', displayname='Fooix Bar')
85
        product = self.factory.makeProduct()
86
87
        # Exact is better than prefix is better than FTI.
88
        self.assertEqual(
89
            [exact_person, prefix_person, contributor_person],
90
            list(self.searchVocabulary(product, u'fooix')))
91
92
        # But karma can bump people up, behind the exact match.
93
        self.addKarma(contributor_person, 500, product=product)
94
        self.assertEqual(
95
            [exact_person, contributor_person, prefix_person],
96
            list(self.searchVocabulary(product, u'fooix')))
97
98
        self.addKarma(prefix_person, 500, product=product)
99
        self.assertEqual(
100
            [exact_person, prefix_person, contributor_person],
101
            list(self.searchVocabulary(product, u'fooix')))
102
103
    def assertKarmaContextConstraint(self, expected, context):
104
        """Check that the karma context constraint works.
105
106
        Confirms that the karma context constraint matches the expected
107
        value, and that a search with it works.
108
        """
109
        if expected is not None:
110
            expected = expected % context.id
111
        self.assertEquals(
112
            expected,
113
            removeSecurityProxy(
114
                self.getVocabulary(context))._karma_context_constraint)
14174.1.1 by Ian Booth
Remove picker feature flags
115
        self.searchVocabulary(context, 'foo')
13171.2.5 by William Grant
Let ValidPersonOrTeamVocabulary order by project affiliation (behind a separate flag).
116
117
    def test_product_karma_context(self):
118
        self.assertKarmaContextConstraint(
119
            'product = %d', self.factory.makeProduct())
120
121
    def test_project_karma_context(self):
122
        self.assertKarmaContextConstraint(
123
            'project = %d', self.factory.makeProject())
124
125
    def test_distribution_karma_context(self):
126
        self.assertKarmaContextConstraint(
127
            'distribution = %d', self.factory.makeDistribution())
128
129
    def test_root_karma_context(self):
130
        self.assertKarmaContextConstraint(None, None)
131
13261.1.2 by William Grant
ValidPersonOrTeamVocabulary's IrcID match is now case-insensitive.
132
    def test_irc_nick_match_is_not_case_sensitive(self):
133
        person = self.factory.makePerson()
134
        irc = getUtility(IIrcIDSet).new(
135
            person, 'somenet', 'MiXeD' + self.factory.getUniqueString())
14174.1.1 by Ian Booth
Remove picker feature flags
136
        self.assertContentEqual(
137
            [person], self.searchVocabulary(person, irc.nickname.lower()))
13261.1.2 by William Grant
ValidPersonOrTeamVocabulary's IrcID match is now case-insensitive.
138
13746.3.1 by Ian Booth
Add filtering to person related vocabs
139
    def _person_filter_tests(self, person):
140
        results = self.searchVocabulary(None, '', 'PERSON')
141
        for personorteam in results:
142
            self.assertFalse(personorteam.is_team)
143
        results = self.searchVocabulary(None, u'fred', 'PERSON')
144
        self.assertEqual([person], list(results))
145
146
    def test_person_filter(self):
14174.1.1 by Ian Booth
Remove picker feature flags
147
        # Test that the person filter only returns people.
13746.3.1 by Ian Booth
Add filtering to person related vocabs
148
        person = self.factory.makePerson(
149
            name="fredperson", email="fredperson@foo.com")
150
        self.factory.makeTeam(
151
            name="fredteam", email="fredteam@foo.com")
152
        self._person_filter_tests(person)
153
14235.2.3 by Ian Booth
Add new ValidPillarOwner vocab for pillar owners and security contacts
154
    def _team_filter_tests(self, teams):
13746.3.1 by Ian Booth
Add filtering to person related vocabs
155
        results = self.searchVocabulary(None, '', 'TEAM')
156
        for personorteam in results:
157
            self.assertTrue(personorteam.is_team)
158
        results = self.searchVocabulary(None, u'fred', 'TEAM')
14235.2.3 by Ian Booth
Add new ValidPillarOwner vocab for pillar owners and security contacts
159
        self.assertContentEqual(teams, list(results))
160
161
162
class TestValidPersonOrTeamVocabulary(ValidPersonOrTeamVocabularyMixin,
163
                                      TestCaseWithFactory):
164
    """Test that the ValidPersonOrTeamVocabulary behaves as expected.
165
166
    Most tests are in lib/lp/registry/doc/vocabularies.txt.
167
    """
168
169
    layer = LaunchpadZopelessLayer
170
    vocabulary_name = 'ValidPersonOrTeam'
13746.3.1 by Ian Booth
Add filtering to person related vocabs
171
172
    def test_team_filter(self):
173
        # Test that the team filter only returns teams.
174
        self.factory.makePerson(
175
            name="fredperson", email="fredperson@foo.com")
176
        team = self.factory.makeTeam(
177
            name="fredteam", email="fredteam@foo.com")
14235.2.3 by Ian Booth
Add new ValidPillarOwner vocab for pillar owners and security contacts
178
        self._team_filter_tests([team])
13746.3.1 by Ian Booth
Add filtering to person related vocabs
179
13171.2.5 by William Grant
Let ValidPersonOrTeamVocabulary order by project affiliation (behind a separate flag).
180
13222.1.1 by William Grant
Test that ValidPersonOrTeamVocabulary's preloading actually works.
181
class TestValidPersonOrTeamPreloading(VocabularyTestBase,
182
                                      TestCaseWithFactory):
183
    """Tests for ValidPersonOrTeamVocabulary's preloading behaviour."""
184
185
    layer = DatabaseFunctionalLayer
186
    vocabulary_name = 'ValidPersonOrTeam'
187
188
    def test_preloads_irc_nicks_and_preferredemail(self):
13222.1.2 by William Grant
Check that it works for people without nicks too.
189
        """Test that IRC nicks and preferred email addresses are preloaded."""
190
        # Create three people with IRC nicks, and one without.
13222.1.1 by William Grant
Test that ValidPersonOrTeamVocabulary's preloading actually works.
191
        people = []
192
        for num in range(3):
193
            person = self.factory.makePerson(displayname='foobar %d' % num)
194
            getUtility(IIrcIDSet).new(person, 'launchpad', person.name)
195
            people.append(person)
13222.1.2 by William Grant
Check that it works for people without nicks too.
196
        people.append(self.factory.makePerson(displayname='foobar 4'))
197
198
        # Remember the current values for checking later, and throw out
199
        # the cache.
13222.1.1 by William Grant
Test that ValidPersonOrTeamVocabulary's preloading actually works.
200
        expected_nicks = dict(
201
            (person.id, list(person.ircnicknames)) for person in people)
202
        expected_emails = dict(
203
            (person.id, person.preferredemail) for person in people)
204
        Store.of(people[0]).invalidate()
205
14174.1.1 by Ian Booth
Remove picker feature flags
206
        results = list(self.searchVocabulary(None, u'foobar'))
13222.1.1 by William Grant
Test that ValidPersonOrTeamVocabulary's preloading actually works.
207
        with StormStatementRecorder() as recorder:
13222.1.2 by William Grant
Check that it works for people without nicks too.
208
            self.assertEquals(4, len(results))
13222.1.1 by William Grant
Test that ValidPersonOrTeamVocabulary's preloading actually works.
209
            for person in results:
210
                self.assertEqual(
211
                    expected_nicks[person.id], person.ircnicknames)
212
                self.assertEqual(
213
                    expected_emails[person.id], person.preferredemail)
214
        self.assertThat(recorder, HasQueryCount(Equals(0)))
215
216
14235.2.3 by Ian Booth
Add new ValidPillarOwner vocab for pillar owners and security contacts
217
class TestValidPersonOrClosedTeamVocabulary(ValidPersonOrTeamVocabularyMixin,
218
                                            TestCaseWithFactory):
219
    """Test that the ValidPersonOrClosedTeamVocabulary behaves as expected."""
220
221
    layer = LaunchpadZopelessLayer
222
    vocabulary_name = 'ValidPillarOwner'
223
224
    def test_team_filter(self):
225
        # Test that the team filter only returns closed teams.
226
        self.factory.makePerson(
227
            name="fredperson", email="fredperson@foo.com")
228
        for policy in OPEN_TEAM_POLICY:
229
            self.factory.makeTeam(
230
                name="fred%s" % policy.name.lower(),
231
                email="team_%s@foo.com" % policy.name,
232
                subscription_policy=policy)
233
        closed_teams = []
234
        for policy in CLOSED_TEAM_POLICY:
235
            closed_teams.append(self.factory.makeTeam(
236
                name="fred%s" % policy.name.lower(),
237
                email="team_%s@foo.com" % policy.name,
238
                subscription_policy=policy))
239
        self._team_filter_tests(closed_teams)
240
241
13171.2.3 by William Grant
Make test_person_vocabularies.VocabularyTestBase more generic.
242
class TeamMemberVocabularyTestBase(VocabularyTestBase):
243
11954.6.21 by Curtis Hovey
Revised text message and escaped quotes.
244
    def test_open_team_cannot_be_a_member_of_a_closed_team(self):
11954.6.11 by Curtis Hovey
Updated ValidTeamMemberVocabulary to prevent open teams from owning closed teams.
245
        context_team = self.factory.makeTeam(
246
            subscription_policy=TeamSubscriptionPolicy.MODERATED)
247
        open_team = self.factory.makeTeam(
248
            subscription_policy=TeamSubscriptionPolicy.OPEN)
249
        moderated_team = self.factory.makeTeam(
250
            subscription_policy=TeamSubscriptionPolicy.MODERATED)
251
        restricted_team = self.factory.makeTeam(
252
            subscription_policy=TeamSubscriptionPolicy.RESTRICTED)
253
        user = self.factory.makePerson()
254
        all_possible_members = self.searchVocabulary(context_team, '')
255
        self.assertNotIn(open_team, all_possible_members)
256
        self.assertIn(moderated_team, all_possible_members)
257
        self.assertIn(restricted_team, all_possible_members)
258
        self.assertIn(user, all_possible_members)
259
11954.6.21 by Curtis Hovey
Revised text message and escaped quotes.
260
    def test_open_team_can_be_a_member_of_an_open_team(self):
11954.6.11 by Curtis Hovey
Updated ValidTeamMemberVocabulary to prevent open teams from owning closed teams.
261
        context_team = self.factory.makeTeam(
262
            subscription_policy=TeamSubscriptionPolicy.OPEN)
263
        open_team = self.factory.makeTeam(
264
            subscription_policy=TeamSubscriptionPolicy.OPEN)
265
        moderated_team = self.factory.makeTeam(
266
            subscription_policy=TeamSubscriptionPolicy.MODERATED)
267
        restricted_team = self.factory.makeTeam(
268
            subscription_policy=TeamSubscriptionPolicy.RESTRICTED)
269
        user = self.factory.makePerson()
270
        all_possible_members = self.searchVocabulary(context_team, '')
271
        self.assertIn(open_team, all_possible_members)
272
        self.assertIn(moderated_team, all_possible_members)
273
        self.assertIn(restricted_team, all_possible_members)
274
        self.assertIn(user, all_possible_members)
275
11954.6.12 by Curtis Hovey
Simplified the vocab displayname because it does not fit in the picker.
276
    def test_vocabulary_displayname(self):
11954.6.11 by Curtis Hovey
Updated ValidTeamMemberVocabulary to prevent open teams from owning closed teams.
277
        context_team = self.factory.makeTeam(
278
            subscription_policy=TeamSubscriptionPolicy.OPEN)
279
        vocabulary = self.getVocabulary(context_team)
280
        self.assertEqual(
11954.6.12 by Curtis Hovey
Simplified the vocab displayname because it does not fit in the picker.
281
            'Select a Team or Person', vocabulary.displayname)
11954.6.11 by Curtis Hovey
Updated ValidTeamMemberVocabulary to prevent open teams from owning closed teams.
282
11954.6.15 by Curtis Hovey
Added step_title to IHugeVocabulary.
283
    def test_open_team_vocabulary_step_title(self):
284
        context_team = self.factory.makeTeam(
285
            subscription_policy=TeamSubscriptionPolicy.OPEN)
286
        vocabulary = self.getVocabulary(context_team)
287
        self.assertEqual('Search', vocabulary.step_title)
288
289
    def test_closed_team_vocabulary_step_title(self):
290
        context_team = self.factory.makeTeam(
291
            subscription_policy=TeamSubscriptionPolicy.MODERATED)
292
        vocabulary = self.getVocabulary(context_team)
293
        self.assertEqual(
11954.6.21 by Curtis Hovey
Revised text message and escaped quotes.
294
            'Search for a restricted team, a moderated team, or a person',
11954.6.15 by Curtis Hovey
Added step_title to IHugeVocabulary.
295
            vocabulary.step_title)
296
11954.6.11 by Curtis Hovey
Updated ValidTeamMemberVocabulary to prevent open teams from owning closed teams.
297
13171.2.3 by William Grant
Make test_person_vocabularies.VocabularyTestBase more generic.
298
class TestValidTeamMemberVocabulary(TeamMemberVocabularyTestBase,
299
                                    TestCaseWithFactory):
11745.1.2 by Edwin Grubbs
Fixed extra_clause placement in query.
300
    """Test that the ValidTeamMemberVocabulary behaves as expected."""
301
11745.1.5 by Edwin Grubbs
Changed test layer.
302
    layer = DatabaseFunctionalLayer
11954.6.6 by Curtis Hovey
Refactored test so that the fixture can be reused.
303
    vocabulary_name = 'ValidTeamMember'
11745.1.2 by Edwin Grubbs
Fixed extra_clause placement in query.
304
305
    def test_public_team_cannot_be_a_member_of_itself(self):
306
        # A public team should be filtered by the vocab.extra_clause
307
        # when provided a search term.
11954.6.6 by Curtis Hovey
Refactored test so that the fixture can be reused.
308
        team = self.factory.makeTeam()
11954.6.7 by Curtis Hovey
Added rule to exclude open teams from ValidTeamMemberVocabulary.
309
        self.assertNotIn(team, self.searchVocabulary(team, team.name))
11745.1.2 by Edwin Grubbs
Fixed extra_clause placement in query.
310
311
    def test_private_team_cannot_be_a_member_of_itself(self):
312
        # A private team should be filtered by the vocab.extra_clause
313
        # when provided a search term.
14494.4.2 by Curtis Hovey
Patched the private team traversal rules back into the tree because merge3 and diff3 suck.
314
        owner = self.factory.makePerson()
11745.1.2 by Edwin Grubbs
Fixed extra_clause placement in query.
315
        team = self.factory.makeTeam(
14494.4.2 by Curtis Hovey
Patched the private team traversal rules back into the tree because merge3 and diff3 suck.
316
            owner=owner, visibility=PersonVisibility.PRIVATE)
317
        login_person(owner)
11954.6.7 by Curtis Hovey
Added rule to exclude open teams from ValidTeamMemberVocabulary.
318
        self.assertNotIn(team, self.searchVocabulary(team, team.name))
319
11954.6.11 by Curtis Hovey
Updated ValidTeamMemberVocabulary to prevent open teams from owning closed teams.
320
13171.2.3 by William Grant
Make test_person_vocabularies.VocabularyTestBase more generic.
321
class TestValidTeamOwnerVocabulary(TeamMemberVocabularyTestBase,
322
                                   TestCaseWithFactory):
11954.6.10 by Curtis Hovey
Added test converage for ValidTeamOwnerVocabulary.
323
    """Test that the ValidTeamOwnerVocabulary behaves as expected."""
324
325
    layer = DatabaseFunctionalLayer
326
    vocabulary_name = 'ValidTeamOwner'
327
328
    def test_team_cannot_own_itself(self):
329
        context_team = self.factory.makeTeam()
330
        results = self.searchVocabulary(context_team, context_team.name)
331
        self.assertNotIn(context_team, results)
332
333
    def test_team_cannot_own_its_owner(self):
334
        context_team = self.factory.makeTeam()
335
        owned_team = self.factory.makeTeam(owner=context_team)
336
        results = self.searchVocabulary(context_team, owned_team.name)
337
        self.assertNotIn(owned_team, results)
13746.3.1 by Ian Booth
Add filtering to person related vocabs
338
339
340
class TestValidPersonVocabulary(VocabularyTestBase,
341
                                      TestCaseWithFactory):
342
    """Test that the ValidPersonVocabulary behaves as expected."""
343
344
    layer = LaunchpadZopelessLayer
345
    vocabulary_name = 'ValidPerson'
346
347
    def test_supported_filters(self):
348
        # The vocab shouldn't support person or team filters.
349
        self.assertEqual([], self.getVocabulary(None).supportedFilters())
350
351
352
class TestValidTeamVocabulary(VocabularyTestBase,
353
                                      TestCaseWithFactory):
354
    """Test that the ValidTeamVocabulary behaves as expected."""
355
356
    layer = LaunchpadZopelessLayer
357
    vocabulary_name = 'ValidTeam'
358
359
    def test_supported_filters(self):
360
        # The vocab shouldn't support person or team filters.
361
        self.assertEqual([], self.getVocabulary(None).supportedFilters())