1
# Copyright 2011 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Test vocabulary adapters."""
8
from datetime import datetime
9
from urllib import urlencode
14
from zope.app.form.interfaces import MissingInputError
15
from zope.component import (
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
25
from canonical.launchpad.interfaces.launchpad import ILaunchpadRoot
26
from canonical.launchpad.webapp.vocabulary import (
30
from canonical.testing.layers import DatabaseFunctionalLayer
31
from lp.app.browser.vocabulary import (
33
MAX_DESCRIPTION_LENGTH,
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 (
42
from lp.testing.views import create_view
45
class PersonPickerEntryAdapterTestCase(TestCaseWithFactory):
47
layer = DatabaseFunctionalLayer
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))
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)
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)
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')
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)
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)
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)
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
picker_expander_enabled=True)
108
self.assertEqual('http://launchpad.dev/~snarf', entry.alt_title_link)
110
['snarf on eg.dom, pting on ex.dom', 'Member since 2005-01-30'],
113
def test_PersonPickerEntryAdapter_enhanced_picker_enabled_team(self):
114
# The enhanced person picker provides more information for teams.
115
team = self.factory.makeTeam(email='fnord@eg.dom', name='fnord')
116
entry = IPickerEntry(team).getPickerEntry(
117
None, enhanced_picker_enabled=True,
118
picker_expander_enabled=True)
119
self.assertEqual('http://launchpad.dev/~fnord', entry.alt_title_link)
120
self.assertEqual(['Team members: 1'], entry.details)
122
def test_PersonPickerEntryAdapter_enhanced_picker_enabled_badges(self):
123
# The enhanced person picker provides affilliation information.
124
person = self.factory.makePerson(email='snarf@eg.dom', name='snarf')
125
project = self.factory.makeProduct(name='fnord', owner=person)
126
bugtask = self.factory.makeBugTask(target=project)
127
entry = IPickerEntry(person).getPickerEntry(
128
bugtask, enhanced_picker_enabled=True,
129
picker_expander_enabled=True)
130
self.assertEqual(1, len(entry.badges))
131
self.assertEqual('/@@/product-badge', entry.badges[0]['url'])
132
self.assertEqual('Affiliated with Fnord', entry.badges[0]['alt'])
135
class TestPersonVocabulary:
136
implements(IHugeVocabulary)
140
def setTestData(cls, person_list):
141
cls.test_persons = person_list
143
def __init__(self, context):
144
self.context = context
146
def toTerm(self, person):
147
return SimpleTerm(person, person.name, person.displayname)
149
def searchForTerms(self, query=None):
151
person for person in self.test_persons if query in person.name]
152
return CountableIterator(len(found), found, self.toTerm)
155
class HugeVocabularyJSONViewTestCase(TestCaseWithFactory):
157
layer = DatabaseFunctionalLayer
160
super(HugeVocabularyJSONViewTestCase, self).setUp()
162
for name in range(1, 7):
164
self.factory.makePerson(name='pting-%s' % name))
165
TestPersonVocabulary.setTestData(test_persons)
166
getSiteManager().registerUtility(
167
TestPersonVocabulary, IVocabularyFactory, 'TestPerson')
169
getSiteManager().unregisterUtility,
170
TestPersonVocabulary, IVocabularyFactory, 'TestPerson')
171
self.addCleanup(TestPersonVocabulary.setTestData, [])
174
def create_vocabulary_view(form):
175
context = getUtility(ILaunchpadRoot)
176
query_string = urlencode(form)
178
context, '+huge-vocabulary', form=form, query_string=query_string)
180
def test_name_field_missing_error(self):
181
view = self.create_vocabulary_view({})
182
self.assertRaisesWithContent(
183
MissingInputError, "('name', '', None)", view.__call__)
185
def test_search_text_field_missing_error(self):
186
view = self.create_vocabulary_view({'name': 'TestPerson'})
187
self.assertRaisesWithContent(
188
MissingInputError, "('search_text', '', None)", view.__call__)
190
def test_vocabulary_name_unknown_error(self):
191
form = dict(name='snarf', search_text='pting')
192
view = self.create_vocabulary_view(form)
193
self.assertRaisesWithContent(
194
UnexpectedFormData, "Unknown vocabulary 'snarf'", view.__call__)
196
def test_json_entries(self):
197
# The results are JSON encoded.
199
'disclosure.picker_enhancements.enabled': 'on',
200
'disclosure.picker_expander.enabled': 'on',
202
flags = FeatureFixture(feature_flag)
204
self.addCleanup(flags.cleanUp)
205
team = self.factory.makeTeam(name='pting-team')
206
TestPersonVocabulary.test_persons.append(team)
207
form = dict(name='TestPerson', search_text='pting-team')
208
view = self.create_vocabulary_view(form)
209
result = simplejson.loads(view())
211
"alt_title": team.name,
212
"alt_title_link": "http://launchpad.dev/~%s" % team.name,
213
"api_uri": "/~%s" % team.name,
214
"css": "sprite team",
215
"details": ['Team members: 1'],
216
"link_css": "sprite new-window",
218
"title": team.displayname,
221
self.assertTrue('entries' in result)
222
self.assertContentEqual(
223
expected.items(), result['entries'][0].items())
225
def test_max_description_size(self):
226
# Descriptions over 120 characters are truncated and ellipsised.
227
email = 'pting-' * 19 + '@example.dom'
228
person = self.factory.makePerson(name='pting-n', email=email)
229
TestPersonVocabulary.test_persons.append(person)
230
# Login to gain permission to know the email address that used
231
# for the description
233
form = dict(name='TestPerson', search_text='pting-n')
234
view = self.create_vocabulary_view(form)
235
result = simplejson.loads(view())
236
expected = (email[:MAX_DESCRIPTION_LENGTH - 3] + '...')
238
'pting-n', result['entries'][0]['value'])
240
expected, result['entries'][0]['description'])
242
def test_default_batch_size(self):
243
# The results are batched.
244
form = dict(name='TestPerson', search_text='pting')
245
view = self.create_vocabulary_view(form)
246
result = simplejson.loads(view())
247
total_size = result['total_size']
248
entries = len(result['entries'])
250
total_size > entries,
251
'total_size: %d is less than entries: %d' % (total_size, entries))
253
def test_batch_size(self):
254
# A The batch size can be specified with the batch param.
256
name='TestPerson', search_text='pting',
257
start='0', batch='1')
258
view = self.create_vocabulary_view(form)
259
result = simplejson.loads(view())
260
self.assertEqual(6, result['total_size'])
261
self.assertEqual(1, len(result['entries']))
263
def test_start_offset(self):
264
# The offset of the batch is specified with the start param.
266
name='TestPerson', search_text='pting',
267
start='1', batch='1')
268
view = self.create_vocabulary_view(form)
269
result = simplejson.loads(view())
270
self.assertEqual(6, result['total_size'])
271
self.assertEqual(1, len(result['entries']))
272
self.assertEqual('pting-2', result['entries'][0]['value'])