~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/app/browser/tests/test_vocabulary.py

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-08-04 04:35:35 UTC
  • mfrom: (13524.2.33 person-picker-expand-0)
  • Revision ID: launchpad@pqm.canonical.com-20110804043535-d9okny8n8t03cyw3
[r=wallyworld][bug=800361] Add an expander to the picker to reveal
 extra details. Apologies jtv for rushing the landing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2011 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Test vocabulary adapters."""
 
5
 
 
6
__metaclass__ = type
 
7
 
 
8
from datetime import datetime
 
9
from urllib import urlencode
 
10
 
 
11
import pytz
 
12
import simplejson
 
13
 
 
14
from zope.app.form.interfaces import MissingInputError
 
15
from zope.component import (
 
16
    getSiteManager,
 
17
    getUtility,
 
18
    )
 
19
from zope.interface import implements
 
20
from zope.schema.interfaces import IVocabularyFactory
 
21
from zope.schema.vocabulary import SimpleTerm
 
22
from zope.security.proxy import removeSecurityProxy
 
23
 
 
24
 
 
25
from canonical.launchpad.interfaces.launchpad import ILaunchpadRoot
 
26
from canonical.launchpad.webapp.vocabulary import (
 
27
    CountableIterator,
 
28
    IHugeVocabulary,
 
29
    )
 
30
from canonical.testing.layers import DatabaseFunctionalLayer
 
31
from lp.app.browser.vocabulary import (
 
32
    IPickerEntry,
 
33
    MAX_DESCRIPTION_LENGTH,
 
34
    )
 
35
from lp.app.errors import UnexpectedFormData
 
36
from lp.registry.interfaces.irc import IIrcIDSet
 
37
from lp.services.features.testing import FeatureFixture
 
38
from lp.testing import (
 
39
    login_person,
 
40
    TestCaseWithFactory,
 
41
    )
 
42
from lp.testing.views import create_view
 
43
 
 
44
 
 
45
class PersonPickerEntryAdapterTestCase(TestCaseWithFactory):
 
46
 
 
47
    layer = DatabaseFunctionalLayer
 
48
 
 
49
    def test_person_to_pickerentry(self):
 
50
        # IPerson can be adpated to IPickerEntry.
 
51
        person = self.factory.makePerson()
 
52
        adapter = IPickerEntry(person)
 
53
        self.assertTrue(IPickerEntry.providedBy(adapter))
 
54
 
 
55
    def test_PersonPickerEntryAdapter_email_anonymous(self):
 
56
        # Anonymous users cannot see entry email addresses.
 
57
        person = self.factory.makePerson(email='snarf@eg.dom')
 
58
        entry = IPickerEntry(person).getPickerEntry(None)
 
59
        self.assertEqual('<email address hidden>', entry.description)
 
60
 
 
61
    def test_PersonPickerEntryAdapter_visible_email_logged_in(self):
 
62
        # Logged in users can see visible email addresses.
 
63
        observer = self.factory.makePerson()
 
64
        login_person(observer)
 
65
        person = self.factory.makePerson(email='snarf@eg.dom')
 
66
        entry = IPickerEntry(person).getPickerEntry(None)
 
67
        self.assertEqual('snarf@eg.dom', entry.description)
 
68
 
 
69
    def test_PersonPickerEntryAdapter_hidden_email_logged_in(self):
 
70
        # Logged in users cannot see hidden email addresses.
 
71
        person = self.factory.makePerson(email='snarf@eg.dom')
 
72
        login_person(person)
 
73
        person.hide_email_addresses = True
 
74
        observer = self.factory.makePerson()
 
75
        login_person(observer)
 
76
        entry = IPickerEntry(person).getPickerEntry(None)
 
77
        self.assertEqual('<email address hidden>', entry.description)
 
78
 
 
79
    def test_PersonPickerEntryAdapter_no_email_logged_in(self):
 
80
        # Teams without email address have no desriptions.
 
81
        team = self.factory.makeTeam()
 
82
        observer = self.factory.makePerson()
 
83
        login_person(observer)
 
84
        entry = IPickerEntry(team).getPickerEntry(None)
 
85
        self.assertEqual(None, entry.description)
 
86
 
 
87
    def test_PersonPickerEntryAdapter_logged_in(self):
 
88
        # Logged in users can see visible email addresses.
 
89
        observer = self.factory.makePerson()
 
90
        login_person(observer)
 
91
        person = self.factory.makePerson(
 
92
            email='snarf@eg.dom', name='snarf')
 
93
        entry = IPickerEntry(person).getPickerEntry(None)
 
94
        self.assertEqual('sprite person', entry.css)
 
95
        self.assertEqual('sprite new-window', entry.link_css)
 
96
 
 
97
    def test_PersonPickerEntryAdapter_enhanced_picker_enabled_user(self):
 
98
        # The enhanced person picker provides more information for users.
 
99
        person = self.factory.makePerson(email='snarf@eg.dom', name='snarf')
 
100
        creation_date = datetime(
 
101
            2005, 01, 30, 0, 0, 0, 0, pytz.timezone('UTC'))
 
102
        removeSecurityProxy(person).datecreated = creation_date
 
103
        getUtility(IIrcIDSet).new(person, 'eg.dom', 'snarf')
 
104
        getUtility(IIrcIDSet).new(person, 'ex.dom', 'pting')
 
105
        entry = IPickerEntry(person).getPickerEntry(
 
106
            None, enhanced_picker_enabled=True)
 
107
        self.assertEqual('http://launchpad.dev/~snarf', entry.alt_title_link)
 
108
        self.assertEqual(
 
109
            ['snarf on eg.dom, pting on ex.dom', 'Member since 2005-01-30'],
 
110
            entry.details)
 
111
 
 
112
    def test_PersonPickerEntryAdapter_enhanced_picker_enabled_team(self):
 
113
        # The enhanced person picker provides more information for teams.
 
114
        team = self.factory.makeTeam(email='fnord@eg.dom', name='fnord')
 
115
        entry = IPickerEntry(team).getPickerEntry(
 
116
            None, enhanced_picker_enabled=True)
 
117
        self.assertEqual('http://launchpad.dev/~fnord', entry.alt_title_link)
 
118
        self.assertEqual(['Team members: 1'], entry.details)
 
119
 
 
120
    def test_PersonPickerEntryAdapter_enhanced_picker_enabled_badges(self):
 
121
        # The enhanced person picker provides affilliation information.
 
122
        person = self.factory.makePerson(email='snarf@eg.dom', name='snarf')
 
123
        project = self.factory.makeProduct(name='fnord', owner=person)
 
124
        bugtask = self.factory.makeBugTask(target=project)
 
125
        entry = IPickerEntry(person).getPickerEntry(
 
126
            bugtask, enhanced_picker_enabled=True)
 
127
        self.assertEqual(1, len(entry.badges))
 
128
        self.assertEqual('/@@/product-badge', entry.badges[0]['url'])
 
129
        self.assertEqual('Affiliated with Fnord', entry.badges[0]['alt'])
 
130
 
 
131
 
 
132
class TestPersonVocabulary:
 
133
    implements(IHugeVocabulary)
 
134
    test_persons = []
 
135
 
 
136
    @classmethod
 
137
    def setTestData(cls, person_list):
 
138
        cls.test_persons = person_list
 
139
 
 
140
    def __init__(self, context):
 
141
        self.context = context
 
142
 
 
143
    def toTerm(self, person):
 
144
        return SimpleTerm(person, person.name, person.displayname)
 
145
 
 
146
    def searchForTerms(self, query=None):
 
147
        found = [
 
148
            person for person in self.test_persons if query in person.name]
 
149
        return CountableIterator(len(found), found, self.toTerm)
 
150
 
 
151
 
 
152
class HugeVocabularyJSONViewTestCase(TestCaseWithFactory):
 
153
 
 
154
    layer = DatabaseFunctionalLayer
 
155
 
 
156
    def setUp(self):
 
157
        super(HugeVocabularyJSONViewTestCase, self).setUp()
 
158
        test_persons = []
 
159
        for name in range(1, 7):
 
160
            test_persons.append(
 
161
                self.factory.makePerson(name='pting-%s' % name))
 
162
        TestPersonVocabulary.setTestData(test_persons)
 
163
        getSiteManager().registerUtility(
 
164
            TestPersonVocabulary, IVocabularyFactory, 'TestPerson')
 
165
        self.addCleanup(
 
166
            getSiteManager().unregisterUtility,
 
167
            TestPersonVocabulary, IVocabularyFactory, 'TestPerson')
 
168
        self.addCleanup(TestPersonVocabulary.setTestData, [])
 
169
 
 
170
    @staticmethod
 
171
    def create_vocabulary_view(form):
 
172
        context = getUtility(ILaunchpadRoot)
 
173
        query_string = urlencode(form)
 
174
        return create_view(
 
175
            context, '+huge-vocabulary', form=form, query_string=query_string)
 
176
 
 
177
    def test_name_field_missing_error(self):
 
178
        view = self.create_vocabulary_view({})
 
179
        self.assertRaisesWithContent(
 
180
            MissingInputError, "('name', '', None)", view.__call__)
 
181
 
 
182
    def test_search_text_field_missing_error(self):
 
183
        view = self.create_vocabulary_view({'name': 'TestPerson'})
 
184
        self.assertRaisesWithContent(
 
185
            MissingInputError, "('search_text', '', None)", view.__call__)
 
186
 
 
187
    def test_vocabulary_name_unknown_error(self):
 
188
        form = dict(name='snarf', search_text='pting')
 
189
        view = self.create_vocabulary_view(form)
 
190
        self.assertRaisesWithContent(
 
191
            UnexpectedFormData, "Unknown vocabulary 'snarf'", view.__call__)
 
192
 
 
193
    def test_json_entries(self):
 
194
        # The results are JSON encoded.
 
195
        feature_flag = {'disclosure.picker_enhancements.enabled': 'on'}
 
196
        flags = FeatureFixture(feature_flag)
 
197
        flags.setUp()
 
198
        self.addCleanup(flags.cleanUp)
 
199
        team = self.factory.makeTeam(name='pting-team')
 
200
        TestPersonVocabulary.test_persons.append(team)
 
201
        form = dict(name='TestPerson', search_text='pting-team')
 
202
        view = self.create_vocabulary_view(form)
 
203
        result = simplejson.loads(view())
 
204
        expected = {
 
205
            "alt_title": team.name,
 
206
            "alt_title_link": "http://launchpad.dev/~%s" % team.name,
 
207
            "api_uri": "/~%s" % team.name,
 
208
            "css": "sprite team",
 
209
            "details": ['Team members: 1'],
 
210
            "link_css": "sprite new-window",
 
211
            "metadata": "team",
 
212
            "title": team.displayname,
 
213
            "value": team.name
 
214
            }
 
215
        self.assertTrue('entries' in result)
 
216
        self.assertContentEqual(
 
217
            expected.items(), result['entries'][0].items())
 
218
 
 
219
    def test_max_description_size(self):
 
220
        # Descriptions over 120 characters are truncated and ellipsised.
 
221
        email = 'pting-' * 19 + '@example.dom'
 
222
        person = self.factory.makePerson(name='pting-n', email=email)
 
223
        TestPersonVocabulary.test_persons.append(person)
 
224
        # Login to gain permission to know the email address that used
 
225
        # for the description
 
226
        login_person(person)
 
227
        form = dict(name='TestPerson', search_text='pting-n')
 
228
        view = self.create_vocabulary_view(form)
 
229
        result = simplejson.loads(view())
 
230
        expected = (email[:MAX_DESCRIPTION_LENGTH - 3] + '...')
 
231
        self.assertEqual(
 
232
            'pting-n', result['entries'][0]['value'])
 
233
        self.assertEqual(
 
234
            expected, result['entries'][0]['description'])
 
235
 
 
236
    def test_default_batch_size(self):
 
237
        # The results are batched.
 
238
        form = dict(name='TestPerson', search_text='pting')
 
239
        view = self.create_vocabulary_view(form)
 
240
        result = simplejson.loads(view())
 
241
        total_size = result['total_size']
 
242
        entries = len(result['entries'])
 
243
        self.assertTrue(
 
244
            total_size > entries,
 
245
            'total_size: %d is less than entries: %d' % (total_size, entries))
 
246
 
 
247
    def test_batch_size(self):
 
248
        # A The batch size can be specified with the batch param.
 
249
        form = dict(
 
250
            name='TestPerson', search_text='pting',
 
251
            start='0', batch='1')
 
252
        view = self.create_vocabulary_view(form)
 
253
        result = simplejson.loads(view())
 
254
        self.assertEqual(6, result['total_size'])
 
255
        self.assertEqual(1, len(result['entries']))
 
256
 
 
257
    def test_start_offset(self):
 
258
        # The offset of the batch is specified with the start param.
 
259
        form = dict(
 
260
            name='TestPerson', search_text='pting',
 
261
            start='1', batch='1')
 
262
        view = self.create_vocabulary_view(form)
 
263
        result = simplejson.loads(view())
 
264
        self.assertEqual(6, result['total_size'])
 
265
        self.assertEqual(1, len(result['entries']))
 
266
        self.assertEqual('pting-2', result['entries'][0]['value'])