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
self.assertEqual('http://launchpad.dev/~snarf', entry.alt_title_link)
109
['snarf on eg.dom, pting on ex.dom', 'Member since 2005-01-30'],
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)
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'])
132
class TestPersonVocabulary:
133
implements(IHugeVocabulary)
137
def setTestData(cls, person_list):
138
cls.test_persons = person_list
140
def __init__(self, context):
141
self.context = context
143
def toTerm(self, person):
144
return SimpleTerm(person, person.name, person.displayname)
146
def searchForTerms(self, query=None):
148
person for person in self.test_persons if query in person.name]
149
return CountableIterator(len(found), found, self.toTerm)
152
class HugeVocabularyJSONViewTestCase(TestCaseWithFactory):
154
layer = DatabaseFunctionalLayer
157
super(HugeVocabularyJSONViewTestCase, self).setUp()
159
for name in range(1, 7):
161
self.factory.makePerson(name='pting-%s' % name))
162
TestPersonVocabulary.setTestData(test_persons)
163
getSiteManager().registerUtility(
164
TestPersonVocabulary, IVocabularyFactory, 'TestPerson')
166
getSiteManager().unregisterUtility,
167
TestPersonVocabulary, IVocabularyFactory, 'TestPerson')
168
self.addCleanup(TestPersonVocabulary.setTestData, [])
171
def create_vocabulary_view(form):
172
context = getUtility(ILaunchpadRoot)
173
query_string = urlencode(form)
175
context, '+huge-vocabulary', form=form, query_string=query_string)
177
def test_name_field_missing_error(self):
178
view = self.create_vocabulary_view({})
179
self.assertRaisesWithContent(
180
MissingInputError, "('name', '', None)", view.__call__)
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__)
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__)
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)
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())
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",
212
"title": team.displayname,
215
self.assertTrue('entries' in result)
216
self.assertContentEqual(
217
expected.items(), result['entries'][0].items())
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
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] + '...')
232
'pting-n', result['entries'][0]['value'])
234
expected, result['entries'][0]['description'])
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'])
244
total_size > entries,
245
'total_size: %d is less than entries: %d' % (total_size, entries))
247
def test_batch_size(self):
248
# A The batch size can be specified with the batch param.
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']))
257
def test_start_offset(self):
258
# The offset of the batch is specified with the start param.
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'])