blob: 59d01c6d93e17973fb0775700d16f938a3111fd1 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2018 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Tests for the projects servicer."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010010import pytest
Copybara854996b2021-09-07 19:36:02 +000011import unittest
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020012try:
13 from mox3 import mox
14except ImportError:
15 import mox
Copybara854996b2021-09-07 19:36:02 +000016
Copybara854996b2021-09-07 19:36:02 +000017from components.prpc import codes
18from components.prpc import context
Copybara854996b2021-09-07 19:36:02 +000019
Copybara854996b2021-09-07 19:36:02 +000020from api.api_proto import common_pb2
21from api.api_proto import features_pb2
22from api.api_proto import features_objects_pb2
Copybara854996b2021-09-07 19:36:02 +000023from framework import exceptions
24from framework import monorailcontext
25from framework import permissions
26from framework import sorting
27from testing import fake
28from testing import testing_helpers
29from tracker import tracker_bizobj
30from services import features_svc
31from services import service_manager
32
Copybara854996b2021-09-07 19:36:02 +000033from api import features_servicer # pylint: disable=ungrouped-imports
34
35
36class FeaturesServicerTest(unittest.TestCase):
37
38 def setUp(self):
39 self.mox = mox.Mox()
40 self.cnxn = fake.MonorailConnection()
41 self.services = service_manager.Services(
42 cache_manager=fake.CacheManager(),
43 config=fake.ConfigService(),
44 issue=fake.IssueService(),
45 user=fake.UserService(),
46 usergroup=fake.UserGroupService(),
47 project=fake.ProjectService(),
48 features=fake.FeaturesService(),
49 hotlist_star=fake.HotlistStarService())
50 sorting.InitializeArtValues(self.services)
51
52 self.project = self.services.project.TestAddProject(
53 'proj', project_id=789, owner_ids=[111], contrib_ids=[222, 333])
54 self.config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
55 self.user1 = self.services.user.TestAddUser('owner@example.com', 111)
56 self.user2 = self.services.user.TestAddUser('editor@example.com', 222)
57 self.user3 = self.services.user.TestAddUser('foo@example.com', 333)
58 self.user4 = self.services.user.TestAddUser('bar@example.com', 444)
59 self.features_svcr = features_servicer.FeaturesServicer(
60 self.services, make_rate_limiter=False)
61 self.prpc_context = context.ServicerContext()
62 self.prpc_context.set_code(codes.StatusCode.OK)
63 self.issue_1 = fake.MakeTestIssue(
64 789, 1, 'sum', 'New', 111, project_name='proj', issue_id=78901)
65 self.issue_2 = fake.MakeTestIssue(
66 789, 2, 'sum', 'Fixed', 111, project_name='proj', issue_id=78902,
67 closed_timestamp=112223344)
68 self.issue_3 = fake.MakeTestIssue(
69 789, 3, 'sum', 'New', 111, project_name='proj', issue_id=78903)
70
71 self.services.issue.TestAddIssue(self.issue_1)
72 self.services.issue.TestAddIssue(self.issue_2)
73 self.services.issue.TestAddIssue(self.issue_3)
74
75 self.project_2 = self.services.project.TestAddProject(
76 'proj2', project_id=788, owner_ids=[111], contrib_ids=[222, 333])
77 self.config_2 = tracker_bizobj.MakeDefaultProjectIssueConfig(788)
78 self.issue_21 = fake.MakeTestIssue(
79 788, 1, 'sum', 'New', 111, project_name='proj2', issue_id=78801)
80 self.issue_22 = fake.MakeTestIssue(
81 788, 2, 'sum', 'New', 111, project_name='proj2', issue_id=78802)
82 self.issue_23 = fake.MakeTestIssue(
83 788, 3, 'sum', 'New', 111, project_name='proj2', issue_id=78803)
84 self.services.issue.TestAddIssue(self.issue_21)
85 self.services.issue.TestAddIssue(self.issue_22)
86 self.services.issue.TestAddIssue(self.issue_23)
87
88 self.PAST_TIME = 123456
89
Copybara854996b2021-09-07 19:36:02 +000090 def tearDown(self):
91 self.mox.UnsetStubs()
92 self.mox.ResetAll()
93
94 def CallWrapped(self, wrapped_handler, *args, **kwargs):
95 return wrapped_handler.wrapped(self.features_svcr, *args, **kwargs)
96
97 def testListHotlistsByUser_SearchByEmail(self):
98 """We can get a list of hotlists for a given email."""
99 # Public hostlist owned by 'owner@example.com'
100 self.services.features.CreateHotlist(
101 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
102 owner_ids=[111], editor_ids=[222])
103
104 # Query for issues for 'owner@example.com'
105 user_ref = common_pb2.UserRef(display_name='owner@example.com')
106 request = features_pb2.ListHotlistsByUserRequest(user=user_ref)
107
108 # We're not authenticated
109 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
110
111 response = self.CallWrapped(self.features_svcr.ListHotlistsByUser, mc,
112 request)
113 self.assertEqual(1, len(response.hotlists))
114 hotlist = response.hotlists[0]
115 self.assertEqual(111, hotlist.owner_ref.user_id)
116 self.assertEqual('ow...@example.com', hotlist.owner_ref.display_name)
117 self.assertEqual('Fake-Hotlist', hotlist.name)
118 self.assertEqual('Summary', hotlist.summary)
119 self.assertEqual('Description', hotlist.description)
120
121 def testListHotlistsByUser_SearchByOwner(self):
122 """We can get a list of hotlists for a given user."""
123 # Public hostlist owned by 'owner@example.com'
124 self.services.features.CreateHotlist(
125 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
126 owner_ids=[111], editor_ids=[222])
127
128 # Query for issues for 'owner@example.com'
129 user_ref = common_pb2.UserRef(user_id=111)
130 request = features_pb2.ListHotlistsByUserRequest(user=user_ref)
131
132 # We're authenticated as 'foo@example.com'
133 mc = monorailcontext.MonorailContext(
134 self.services, cnxn=self.cnxn, requester='foo@example.com')
135
136 response = self.CallWrapped(self.features_svcr.ListHotlistsByUser, mc,
137 request)
138 self.assertEqual(1, len(response.hotlists))
139 hotlist = response.hotlists[0]
140 self.assertEqual(111, hotlist.owner_ref.user_id)
141 # User1 and user3 share self.project.
142 self.assertEqual('owner@example.com', hotlist.owner_ref.display_name)
143 self.assertEqual('Fake-Hotlist', hotlist.name)
144 self.assertEqual('Summary', hotlist.summary)
145 self.assertEqual('Description', hotlist.description)
146
147 def testListHotlistsByUser_SearchByEditor(self):
148 """We can get a list of hotlists for a given user."""
149 # Public hostlist owned by 'owner@example.com'
150 self.services.features.CreateHotlist(
151 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
152 owner_ids=[111], editor_ids=[222])
153
154 # Query for issues for 'editor@example.com'
155 user_ref = common_pb2.UserRef(user_id=222)
156 request = features_pb2.ListHotlistsByUserRequest(user=user_ref)
157
158 # We're authenticated as 'foo@example.com'
159 mc = monorailcontext.MonorailContext(
160 self.services, cnxn=self.cnxn, requester='foo@example.com')
161
162 response = self.CallWrapped(self.features_svcr.ListHotlistsByUser, mc,
163 request)
164 self.assertEqual(1, len(response.hotlists))
165 hotlist = response.hotlists[0]
166 self.assertEqual(111, hotlist.owner_ref.user_id)
167 # User1 and user3 share self.project.
168 self.assertEqual('owner@example.com', hotlist.owner_ref.display_name)
169 self.assertEqual('Fake-Hotlist', hotlist.name)
170 self.assertEqual('Summary', hotlist.summary)
171 self.assertEqual('Description', hotlist.description)
172
173 def testListHotlistsByUser_NotSignedIn(self):
174 # Public hostlist owned by 'owner@example.com'
175 self.services.features.CreateHotlist(
176 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
177 owner_ids=[111], editor_ids=[222])
178
179 # Query for issues for 'owner@example.com'
180 user_ref = common_pb2.UserRef(user_id=111)
181 request = features_pb2.ListHotlistsByUserRequest(user=user_ref)
182
183 # We're not authenticated
184 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
185 response = self.CallWrapped(self.features_svcr.ListHotlistsByUser, mc,
186 request)
187
188 self.assertEqual(1, len(response.hotlists))
189 hotlist = response.hotlists[0]
190 self.assertEqual(111, hotlist.owner_ref.user_id)
191
192 def testListHotlistsByUser_Empty(self):
193 """There are no hotlists for the given user."""
194 # Public hostlist owned by 'owner@example.com'
195 self.services.features.CreateHotlist(
196 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
197 owner_ids=[111], editor_ids=[222])
198
199 # Query for issues for 'bar@example.com'
200 user_ref = common_pb2.UserRef(user_id=444)
201 request = features_pb2.ListHotlistsByUserRequest(user=user_ref)
202
203 # We're authenticated as 'foo@example.com'
204 mc = monorailcontext.MonorailContext(
205 self.services, cnxn=self.cnxn, requester='foo@example.com')
206 response = self.CallWrapped(self.features_svcr.ListHotlistsByUser, mc,
207 request)
208
209 self.assertEqual(0, len(response.hotlists))
210
211 def testListHotlistsByUser_NoHotlists(self):
212 """There are no hotlists."""
213 # No hotlists
214 # Query for issues for 'owner@example.com'
215 user_ref = common_pb2.UserRef(user_id=111)
216 request = features_pb2.ListHotlistsByUserRequest(user=user_ref)
217
218 # We're authenticated as 'foo@example.com'
219 mc = monorailcontext.MonorailContext(
220 self.services, cnxn=self.cnxn, requester='foo@example.com')
221 response = self.CallWrapped(self.features_svcr.ListHotlistsByUser, mc,
222 request)
223 self.assertEqual(0, len(response.hotlists))
224
225 def testListHotlistsByUser_PrivateIssueAsOwner(self):
226 # Private hostlist owned by 'owner@example.com'
227 self.services.features.CreateHotlist(
228 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
229 owner_ids=[111], editor_ids=[222], is_private=True)
230
231 # Query for issues for 'owner@example.com'
232 user_ref = common_pb2.UserRef(user_id=111)
233 request = features_pb2.ListHotlistsByUserRequest(user=user_ref)
234
235 # We're authenticated as 'owner@example.com'
236 mc = monorailcontext.MonorailContext(
237 self.services, cnxn=self.cnxn, requester='owner@example.com')
238 response = self.CallWrapped(self.features_svcr.ListHotlistsByUser, mc,
239 request)
240
241 self.assertEqual(1, len(response.hotlists))
242 hotlist = response.hotlists[0]
243 self.assertEqual(111, hotlist.owner_ref.user_id)
244
245 def testListHotlistsByUser_PrivateIssueAsEditor(self):
246 # Private hostlist owned by 'owner@example.com'
247 self.services.features.CreateHotlist(
248 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
249 owner_ids=[111], editor_ids=[222], is_private=True)
250
251 # Query for issues for 'owner@example.com'
252 user_ref = common_pb2.UserRef(user_id=111)
253 request = features_pb2.ListHotlistsByUserRequest(user=user_ref)
254
255 # We're authenticated as 'editor@example.com'
256 mc = monorailcontext.MonorailContext(
257 self.services, cnxn=self.cnxn, requester='editor@example.com')
258 response = self.CallWrapped(self.features_svcr.ListHotlistsByUser, mc,
259 request)
260
261 self.assertEqual(1, len(response.hotlists))
262 hotlist = response.hotlists[0]
263 self.assertEqual(111, hotlist.owner_ref.user_id)
264
265 def testListHotlistsByUser_PrivateIssueNoAccess(self):
266 # Private hostlist owned by 'owner@example.com'
267 self.services.features.CreateHotlist(
268 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
269 owner_ids=[111], editor_ids=[222], is_private=True)
270
271 # Query for issues for 'owner@example.com'
272 user_ref = common_pb2.UserRef(user_id=111)
273 request = features_pb2.ListHotlistsByUserRequest(user=user_ref)
274
275 # We're authenticated as 'foo@example.com'
276 mc = monorailcontext.MonorailContext(
277 self.services, cnxn=self.cnxn, requester='foo@example.com')
278 response = self.CallWrapped(self.features_svcr.ListHotlistsByUser, mc,
279 request)
280
281 self.assertEqual(0, len(response.hotlists))
282
283 def testListHotlistsByUser_PrivateIssueNotSignedIn(self):
284 # Private hostlist owned by 'owner@example.com'
285 self.services.features.CreateHotlist(
286 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
287 owner_ids=[111], editor_ids=[222], is_private=True)
288
289 # Query for issues for 'owner@example.com'
290 user_ref = common_pb2.UserRef(user_id=111)
291 request = features_pb2.ListHotlistsByUserRequest(user=user_ref)
292
293 # We're not authenticated
294 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
295 response = self.CallWrapped(self.features_svcr.ListHotlistsByUser, mc,
296 request)
297
298 self.assertEqual(0, len(response.hotlists))
299
300 def AddIssueToHotlist(self, hotlist_id, issue_id=78901, adder_id=111):
301 self.services.features.AddIssuesToHotlists(
302 self.cnxn, [hotlist_id], [(issue_id, adder_id, 0, '')],
303 None, None, None)
304
305 def testListHotlistsByIssue_Normal(self):
306 hotlist = self.services.features.CreateHotlist(
307 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
308 owner_ids=[111], editor_ids=[222])
309 self.AddIssueToHotlist(hotlist.hotlist_id)
310
311 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
312 request = features_pb2.ListHotlistsByIssueRequest(issue=issue_ref)
313
314 mc = monorailcontext.MonorailContext(
315 self.services, cnxn=self.cnxn, requester='foo@example.com')
316 response = self.CallWrapped(self.features_svcr.ListHotlistsByIssue, mc,
317 request)
318
319 self.assertEqual(1, len(response.hotlists))
320 hotlist = response.hotlists[0]
321 self.assertEqual('Fake-Hotlist', hotlist.name)
322
323 def testListHotlistsByIssue_NotSignedIn(self):
324 # Public hostlist owned by 'owner@example.com'
325 hotlist = self.services.features.CreateHotlist(
326 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
327 owner_ids=[111], editor_ids=[222])
328 self.AddIssueToHotlist(hotlist.hotlist_id)
329
330 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
331 request = features_pb2.ListHotlistsByIssueRequest(issue=issue_ref)
332
333 # We're not authenticated
334 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
335 response = self.CallWrapped(self.features_svcr.ListHotlistsByIssue, mc,
336 request)
337
338 self.assertEqual(1, len(response.hotlists))
339 hotlist = response.hotlists[0]
340 self.assertEqual('Fake-Hotlist', hotlist.name)
341
342 def testListHotlistsByIssue_Empty(self):
343 """There are no hotlists with the given issue."""
344 # Public hostlist owned by 'owner@example.com'
345 self.services.features.CreateHotlist(
346 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
347 owner_ids=[111], editor_ids=[222])
348
349 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
350 request = features_pb2.ListHotlistsByIssueRequest(issue=issue_ref)
351
352 # We're authenticated as 'foo@example.com'
353 mc = monorailcontext.MonorailContext(
354 self.services, cnxn=self.cnxn, requester='foo@example.com')
355 response = self.CallWrapped(self.features_svcr.ListHotlistsByIssue, mc,
356 request)
357
358 self.assertEqual(0, len(response.hotlists))
359
360 def testListHotlistsByIssue_NoHotlists(self):
361 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
362 request = features_pb2.ListHotlistsByIssueRequest(issue=issue_ref)
363
364 # We're authenticated as 'foo@example.com'
365 mc = monorailcontext.MonorailContext(
366 self.services, cnxn=self.cnxn, requester='foo@example.com')
367 response = self.CallWrapped(self.features_svcr.ListHotlistsByIssue, mc,
368 request)
369 self.assertEqual(0, len(response.hotlists))
370
371 def testListHotlistsByIssue_PrivateHotlistAsOwner(self):
372 """An owner can view their private issues."""
373 # Private hostlist owned by 'owner@example.com'
374 hotlist = self.services.features.CreateHotlist(
375 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
376 owner_ids=[111], editor_ids=[222], is_private=True)
377 self.AddIssueToHotlist(hotlist.hotlist_id)
378
379 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
380 request = features_pb2.ListHotlistsByIssueRequest(issue=issue_ref)
381
382 # We're authenticated as 'owner@example.com'
383 mc = monorailcontext.MonorailContext(
384 self.services, cnxn=self.cnxn, requester='owner@example.com')
385 response = self.CallWrapped(self.features_svcr.ListHotlistsByIssue, mc,
386 request)
387
388 self.assertEqual(1, len(response.hotlists))
389 hotlist = response.hotlists[0]
390 self.assertEqual('Fake-Hotlist', hotlist.name)
391
392 def testListHotlistsByIssue_PrivateHotlistNoAccess(self):
393 # Private hostlist owned by 'owner@example.com'
394 hotlist = self.services.features.CreateHotlist(
395 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
396 owner_ids=[111], editor_ids=[222], is_private=True)
397 self.AddIssueToHotlist(hotlist.hotlist_id)
398
399 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
400 request = features_pb2.ListHotlistsByIssueRequest(issue=issue_ref)
401
402 # We're authenticated as 'foo@example.com'
403 mc = monorailcontext.MonorailContext(
404 self.services, cnxn=self.cnxn, requester='foo@example.com')
405 response = self.CallWrapped(self.features_svcr.ListHotlistsByIssue, mc,
406 request)
407
408 self.assertEqual(0, len(response.hotlists))
409
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100410 @pytest.mark.skip(reason='Test is flaky (https://crbug.com/monorail/12052)')
Copybara854996b2021-09-07 19:36:02 +0000411 def testListHotlistsByIssue_NonProjectHotlists(self):
412 hotlist = self.services.features.CreateHotlist(
413 self.cnxn,
414 'Fake-Hotlist',
415 'Summary',
416 'Description',
417 owner_ids=[111],
418 editor_ids=[222])
419 spam_hotlist = self.services.features.CreateHotlist(
420 self.cnxn,
421 'Spam-Hotlist',
422 'Summary',
423 'Description',
424 owner_ids=[444],
425 editor_ids=[])
426 another_hotlist = self.services.features.CreateHotlist(
427 self.cnxn,
428 'Another-Hotlist',
429 'Summary',
430 'Description',
431 owner_ids=[111],
432 editor_ids=[])
433 self.AddIssueToHotlist(hotlist.hotlist_id)
434 self.AddIssueToHotlist(spam_hotlist.hotlist_id)
435 self.AddIssueToHotlist(another_hotlist.hotlist_id)
436
437 issue_ref = common_pb2.IssueRef(project_name='proj', local_id=1)
438 request = features_pb2.ListHotlistsByIssueRequest(issue=issue_ref)
439
440 mc = monorailcontext.MonorailContext(
441 self.services, cnxn=self.cnxn, requester='foo@example.com')
442 response = self.CallWrapped(
443 self.features_svcr.ListHotlistsByIssue, mc, request)
444
445 self.assertEqual(2, len(response.hotlists))
446 self.assertEqual('Fake-Hotlist', response.hotlists[0].name)
447 self.assertEqual('Another-Hotlist', response.hotlists[1].name)
448
449 def testListRecentlyVisitedHotlists(self):
450 hotlists = [
451 self.services.features.CreateHotlist(
452 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
453 owner_ids=[self.user2.user_id], editor_ids=[self.user1.user_id],
454 default_col_spec='chicken'),
455 self.services.features.CreateHotlist(
456 self.cnxn, 'Fake-Hotlist-2', 'Summary', 'Description',
457 owner_ids=[self.user1.user_id], editor_ids=[self.user2.user_id],
458 default_col_spec='honk'),
459 self.services.features.CreateHotlist(
460 self.cnxn, 'Private-Hotlist', 'Summary', 'Description',
461 owner_ids=[self.user3.user_id], editor_ids=[self.user2.user_id],
462 is_private=True)]
463
464 for hotlist in hotlists:
465 self.services.user.AddVisitedHotlist(
466 self.cnxn, self.user1.user_id, hotlist.hotlist_id)
467
468 request = features_pb2.ListRecentlyVisitedHotlistsRequest()
469 mc = monorailcontext.MonorailContext(
470 self.services, cnxn=self.cnxn, requester=self.user1.email)
471 response = self.CallWrapped(
472 self.features_svcr.ListRecentlyVisitedHotlists, mc, request)
473
474 expected_hotlists = [
475 features_objects_pb2.Hotlist(
476 owner_ref=common_pb2.UserRef(
477 user_id=self.user2.user_id, display_name=self.user2.email),
478 editor_refs=[
479 common_pb2.UserRef(
480 user_id=self.user1.user_id, display_name=self.user1.email)
481 ],
482 name='Fake-Hotlist',
483 summary='Summary',
484 description='Description',
485 is_private=False,
486 default_col_spec='chicken'),
487 features_objects_pb2.Hotlist(
488 owner_ref=common_pb2.UserRef(
489 user_id=self.user1.user_id, display_name=self.user1.email),
490 editor_refs=[
491 common_pb2.UserRef(
492 user_id=self.user2.user_id, display_name=self.user2.email)
493 ],
494 name='Fake-Hotlist-2',
495 summary='Summary',
496 description='Description',
497 is_private=False,
498 default_col_spec='honk')
499 ]
500
501 # We don't have permission to see the last issue, because it is marked as
502 # private and we're not owners or editors.
503 self.assertEqual(expected_hotlists, list(response.hotlists))
504
505 def testListRecentlyVisitedHotlists_Anon(self):
506 request = features_pb2.ListRecentlyVisitedHotlistsRequest()
507 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
508 response = self.CallWrapped(
509 self.features_svcr.ListRecentlyVisitedHotlists, mc, request)
510 self.assertEqual(0, len(response.hotlists))
511
512 def testListStarredHotlists(self):
513 hotlists = [
514 self.services.features.CreateHotlist(
515 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
516 owner_ids=[self.user2.user_id], editor_ids=[self.user1.user_id],
517 default_col_spec='cow chicken'),
518 self.services.features.CreateHotlist(
519 self.cnxn, 'Fake-Hotlist-2', 'Summary', 'Description',
520 owner_ids=[self.user1.user_id],
521 editor_ids=[self.user2.user_id, self.user3.user_id],
522 default_col_spec=''),
523 self.services.features.CreateHotlist(
524 self.cnxn, 'Private-Hotlist', 'Summary', 'Description',
525 owner_ids=[self.user3.user_id], editor_ids=[self.user2.user_id],
526 is_private=True, default_col_spec='chicken')]
527
528 for hotlist in hotlists:
529 self.services.hotlist_star.SetStar(
530 self.cnxn, hotlist.hotlist_id, self.user1.user_id, True)
531
532 request = features_pb2.ListStarredHotlistsRequest()
533 mc = monorailcontext.MonorailContext(
534 self.services, cnxn=self.cnxn, requester='owner@example.com')
535 response = self.CallWrapped(
536 self.features_svcr.ListStarredHotlists, mc, request)
537
538 expected_hotlists = [
539 features_objects_pb2.Hotlist(
540 owner_ref=common_pb2.UserRef(
541 user_id=self.user2.user_id, display_name=self.user2.email),
542 editor_refs=[
543 common_pb2.UserRef(
544 user_id=self.user1.user_id, display_name=self.user1.email)
545 ],
546 name='Fake-Hotlist',
547 summary='Summary',
548 description='Description',
549 is_private=False,
550 default_col_spec='cow chicken'),
551 features_objects_pb2.Hotlist(
552 owner_ref=common_pb2.UserRef(
553 user_id=self.user1.user_id, display_name=self.user1.email),
554 editor_refs=[
555 common_pb2.UserRef(
556 user_id=self.user2.user_id, display_name=self.user2.email),
557 common_pb2.UserRef(
558 user_id=self.user3.user_id, display_name=self.user3.email)
559 ],
560 name='Fake-Hotlist-2',
561 summary='Summary',
562 description='Description',
563 is_private=False)
564 ]
565
566 # We don't have permission to see the last issue, because it is marked as
567 # private and we're not owners or editors.
568 self.assertEqual(expected_hotlists, list(response.hotlists))
569
570 def testListStarredHotlists_Anon(self):
571 request = features_pb2.ListStarredHotlistsRequest()
572 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
573 response = self.CallWrapped(
574 self.features_svcr.ListStarredHotlists, mc, request)
575 self.assertEqual(0, len(response.hotlists))
576
577 def CallGetStarCount(self):
578 # Query for hotlists for 'owner@example.com'
579 owner_ref = common_pb2.UserRef(user_id=111)
580 hotlist_ref = common_pb2.HotlistRef(name='Fake-Hotlist', owner=owner_ref)
581 request = features_pb2.GetHotlistStarCountRequest(hotlist_ref=hotlist_ref)
582 mc = monorailcontext.MonorailContext(
583 self.services, cnxn=self.cnxn, requester='owner@example.com')
584 response = self.CallWrapped(
585 self.features_svcr.GetHotlistStarCount, mc, request)
586 return response.star_count
587
588 def CallStar(self, requester='owner@example.com', starred=True):
589 # Query for hotlists for 'owner@example.com'
590 owner_ref = common_pb2.UserRef(user_id=111)
591 hotlist_ref = common_pb2.HotlistRef(name='Fake-Hotlist', owner=owner_ref)
592 request = features_pb2.StarHotlistRequest(
593 hotlist_ref=hotlist_ref, starred=starred)
594 mc = monorailcontext.MonorailContext(
595 self.services, cnxn=self.cnxn, requester=requester)
596 response = self.CallWrapped(
597 self.features_svcr.StarHotlist, mc, request)
598 return response.star_count
599
600 def testStarCount_Normal(self):
601 self.services.features.CreateHotlist(
602 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
603 owner_ids=[111], editor_ids=[222])
604 self.assertEqual(0, self.CallGetStarCount())
605 self.assertEqual(1, self.CallStar())
606 self.assertEqual(1, self.CallGetStarCount())
607
608 def testStarCount_StarTwiceSameUser(self):
609 self.services.features.CreateHotlist(
610 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
611 owner_ids=[111], editor_ids=[222])
612 self.assertEqual(1, self.CallStar())
613 self.assertEqual(1, self.CallStar())
614 self.assertEqual(1, self.CallGetStarCount())
615
616 def testStarCount_StarTwiceDifferentUser(self):
617 self.services.features.CreateHotlist(
618 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
619 owner_ids=[111], editor_ids=[222])
620 self.assertEqual(1, self.CallStar())
621 self.assertEqual(2, self.CallStar(requester='user_222@example.com'))
622 self.assertEqual(2, self.CallGetStarCount())
623
624 def testStarCount_RemoveStarTwiceSameUser(self):
625 self.services.features.CreateHotlist(
626 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
627 owner_ids=[111], editor_ids=[222])
628 self.assertEqual(1, self.CallStar())
629 self.assertEqual(1, self.CallGetStarCount())
630
631 self.assertEqual(0, self.CallStar(starred=False))
632 self.assertEqual(0, self.CallStar(starred=False))
633 self.assertEqual(0, self.CallGetStarCount())
634
635 def testStarCount_RemoveStarTwiceDifferentUser(self):
636 self.services.features.CreateHotlist(
637 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
638 owner_ids=[111], editor_ids=[222])
639 self.assertEqual(1, self.CallStar())
640 self.assertEqual(2, self.CallStar(requester='user_222@example.com'))
641 self.assertEqual(2, self.CallGetStarCount())
642
643 self.assertEqual(1, self.CallStar(starred=False))
644 self.assertEqual(
645 0, self.CallStar(requester='user_222@example.com', starred=False))
646 self.assertEqual(0, self.CallGetStarCount())
647
648 def testGetHotlist(self):
649 hotlist = self.services.features.CreateHotlist(
650 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
651 owner_ids=[self.user3.user_id], editor_ids=[self.user4.user_id],
652 is_private=True, default_col_spec='corgi butts')
653
654 owner_ref = common_pb2.UserRef(user_id=self.user3.user_id)
655 hotlist_ref = common_pb2.HotlistRef(
656 name=hotlist.name, owner=owner_ref)
657 request = features_pb2.GetHotlistRequest(hotlist_ref=hotlist_ref)
658
659 mc = monorailcontext.MonorailContext(
660 self.services, cnxn=self.cnxn, requester=self.user4.email)
661 mc.LookupLoggedInUserPerms(None)
662 response = self.CallWrapped(
663 self.features_svcr.GetHotlist, mc, request)
664
665 self.assertEqual(
666 response.hotlist,
667 features_objects_pb2.Hotlist(
668 owner_ref=common_pb2.UserRef(
669 user_id=self.user3.user_id,
670 display_name=testing_helpers.ObscuredEmail(self.user3.email)),
671 editor_refs=[common_pb2.UserRef(
672 user_id=self.user4.user_id,
673 display_name=self.user4.email)],
674 name=hotlist.name,
675 summary=hotlist.summary,
676 description=hotlist.description,
677 default_col_spec='corgi butts',
678 is_private=True))
679
680 def testGetHotlist_BadInput(self):
681 hotlist_ref = common_pb2.HotlistRef()
682 request = features_pb2.GetHotlistRequest(hotlist_ref=hotlist_ref)
683
684 mc = monorailcontext.MonorailContext(
685 self.services, cnxn=self.cnxn, requester='foo@example.com')
686 with self.assertRaises(features_svc.NoSuchHotlistException):
687 self.CallWrapped(self.features_svcr.GetHotlist, mc, request)
688
689 def testCreateHotlist_Normal(self):
690 request = features_pb2.CreateHotlistRequest(
691 name='Fake-Hotlist',
692 summary='Summary',
693 description='Description',
694 editor_refs=[
695 common_pb2.UserRef(user_id=222),
696 common_pb2.UserRef(display_name='foo@example.com')],
697 issue_refs=[
698 common_pb2.IssueRef(project_name='proj', local_id=1),
699 common_pb2.IssueRef(project_name='proj', local_id=2)],
700 is_private=True)
701 mc = monorailcontext.MonorailContext(
702 self.services, cnxn=self.cnxn, requester='owner@example.com')
703 self.CallWrapped(self.features_svcr.CreateHotlist, mc, request)
704
705 # Check that the hotlist was successfuly added.
706 hotlist_id = self.services.features.LookupHotlistIDs(
707 self.cnxn, ['Fake-Hotlist'], [111]).get(('fake-hotlist', 111))
708 hotlist = self.services.features.GetHotlist(self.cnxn, hotlist_id)
709 self.assertEqual('Summary', hotlist.summary)
710 self.assertEqual('Description', hotlist.description)
711 self.assertEqual([111], hotlist.owner_ids)
712 self.assertEqual([222, 333], hotlist.editor_ids)
713 self.assertEqual(
714 [self.issue_1.issue_id, self.issue_2.issue_id],
715 [item.issue_id for item in hotlist.items])
716 self.assertTrue(hotlist.is_private)
717
718 def testCreateHotlist_Simple(self):
719 request = features_pb2.CreateHotlistRequest(
720 name='Fake-Hotlist',
721 summary='Summary',
722 description='Description')
723 mc = monorailcontext.MonorailContext(
724 self.services, cnxn=self.cnxn, requester='owner@example.com')
725 self.CallWrapped(self.features_svcr.CreateHotlist, mc, request)
726
727 # Check that the hotlist was successfuly added.
728 hotlist_id = self.services.features.LookupHotlistIDs(
729 self.cnxn, ['Fake-Hotlist'], [111]).get(('fake-hotlist', 111))
730 hotlist = self.services.features.GetHotlist(self.cnxn, hotlist_id)
731 self.assertEqual('Summary', hotlist.summary)
732 self.assertEqual('Description', hotlist.description)
733 self.assertEqual([111], hotlist.owner_ids)
734 self.assertEqual([], hotlist.editor_ids)
735 self.assertEqual(0, len(hotlist.items))
736 self.assertFalse(hotlist.is_private)
737
738 def testCheckHotlistName_OK(self):
739 request = features_pb2.CheckHotlistNameRequest(name='Fake-Hotlist')
740 mc = monorailcontext.MonorailContext(
741 self.services, cnxn=self.cnxn, requester='owner@example.com')
742 result = self.CallWrapped(self.features_svcr.CheckHotlistName, mc, request)
743 self.assertEqual('', result.error)
744
745 def testCheckHotlistName_Anon(self):
746 request = features_pb2.CheckHotlistNameRequest(name='Fake-Hotlist')
747 mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
748
749 with self.assertRaises(exceptions.InputException):
750 self.CallWrapped(self.features_svcr.CheckHotlistName, mc, request)
751
752 def testCheckHotlistName_InvalidName(self):
753 request = features_pb2.CheckHotlistNameRequest(name='**Invalid**')
754 mc = monorailcontext.MonorailContext(
755 self.services, cnxn=self.cnxn, requester='owner@example.com')
756
757 result = self.CallWrapped(self.features_svcr.CheckHotlistName, mc, request)
758 self.assertNotEqual('', result.error)
759
760 def testCheckHotlistName_AlreadyExists(self):
761 self.services.features.CreateHotlist(
762 self.cnxn, 'Fake-Hotlist', 'Summary', 'Description',
763 owner_ids=[111], editor_ids=[])
764
765 request = features_pb2.CheckHotlistNameRequest(name='Fake-Hotlist')
766 mc = monorailcontext.MonorailContext(
767 self.services, cnxn=self.cnxn, requester='owner@example.com')
768
769 result = self.CallWrapped(self.features_svcr.CheckHotlistName, mc, request)
770 self.assertNotEqual('', result.error)
771
772 def testRemoveIssuesFromHotlists(self):
773 # Create two hotlists with issues 1 and 2.
774 hotlist_1 = self.services.features.CreateHotlist(
775 self.cnxn, 'Hotlist-1', 'Summary', 'Description', owner_ids=[111],
776 editor_ids=[])
777 hotlist_2 = self.services.features.CreateHotlist(
778 self.cnxn, 'Hotlist-2', 'Summary', 'Description', owner_ids=[111],
779 editor_ids=[])
780 self.services.features.AddIssuesToHotlists(
781 self.cnxn,
782 [hotlist_1.hotlist_id, hotlist_2.hotlist_id],
783 [(self.issue_1.issue_id, 111, 0, ''),
784 (self.issue_2.issue_id, 111, 0, '')],
785 None, None, None)
786
787 # Remove Issue 1 from both hotlists.
788 request = features_pb2.RemoveIssuesFromHotlistsRequest(
789 hotlist_refs=[
790 common_pb2.HotlistRef(
791 name='Hotlist-1',
792 owner=common_pb2.UserRef(user_id=111)),
793 common_pb2.HotlistRef(
794 name='Hotlist-2',
795 owner=common_pb2.UserRef(user_id=111))],
796 issue_refs=[
797 common_pb2.IssueRef(project_name='proj', local_id=1)])
798
799 mc = monorailcontext.MonorailContext(
800 self.services, cnxn=self.cnxn, requester='owner@example.com')
801 self.CallWrapped(self.features_svcr.RemoveIssuesFromHotlists, mc, request)
802
803 # Only Issue 2 should remain in both lists.
804 self.assertEqual(
805 [self.issue_2.issue_id],
806 [item.issue_id for item in hotlist_1.items])
807 self.assertEqual(
808 [self.issue_2.issue_id],
809 [item.issue_id for item in hotlist_2.items])
810
811 def testAddIssuesToHotlists(self):
812 # Create two hotlists
813 hotlist_1 = self.services.features.CreateHotlist(
814 self.cnxn, 'Hotlist-1', 'Summary', 'Description', owner_ids=[111],
815 editor_ids=[])
816 hotlist_2 = self.services.features.CreateHotlist(
817 self.cnxn, 'Hotlist-2', 'Summary', 'Description', owner_ids=[111],
818 editor_ids=[])
819
820 # Add Issue 1 to both hotlists
821 request = features_pb2.AddIssuesToHotlistsRequest(
822 note='Foo',
823 hotlist_refs=[
824 common_pb2.HotlistRef(
825 name='Hotlist-1',
826 owner=common_pb2.UserRef(user_id=111)),
827 common_pb2.HotlistRef(
828 name='Hotlist-2',
829 owner=common_pb2.UserRef(user_id=111))],
830 issue_refs=[
831 common_pb2.IssueRef(project_name='proj', local_id=1)])
832
833 mc = monorailcontext.MonorailContext(
834 self.services, cnxn=self.cnxn, requester='owner@example.com')
835 self.CallWrapped(self.features_svcr.AddIssuesToHotlists, mc, request)
836
837 self.assertEqual(
838 [self.issue_1.issue_id],
839 [item.issue_id for item in hotlist_1.items])
840 self.assertEqual(
841 [self.issue_1.issue_id],
842 [item.issue_id for item in hotlist_2.items])
843
844 self.assertEqual('Foo', hotlist_1.items[0].note)
845 self.assertEqual('Foo', hotlist_2.items[0].note)
846
847 def testRerankHotlistIssues(self):
848 """Rerank a hotlist."""
849 issue_3 = fake.MakeTestIssue(
850 789, 3, 'sum', 'New', 111, project_name='proj', issue_id=78903)
851 issue_4 = fake.MakeTestIssue(
852 789, 4, 'sum', 'New', 111, project_name='proj', issue_id=78904)
853 self.services.issue.TestAddIssue(issue_3)
854 self.services.issue.TestAddIssue(issue_4)
855
856 owner_ids = [self.user1.user_id]
857 follower_ids = [self.user2.user_id]
858 editor_ids = [self.user3.user_id]
859 hotlist_items = [
860 (78904, 31, self.user2.user_id, self.PAST_TIME, 'note'),
861 (78903, 21, self.user2.user_id, self.PAST_TIME, 'note'),
862 (78902, 11, self.user2.user_id, self.PAST_TIME, 'note'),
863 (78901, 1, self.user2.user_id, self.PAST_TIME, 'note')]
864 hotlist = self.services.features.TestAddHotlist(
865 'RerankHotlistName', summary='summary', owner_ids=owner_ids,
866 editor_ids=editor_ids, follower_ids=follower_ids,
867 hotlist_id=1236, hotlist_item_fields=hotlist_items)
868
869 request = features_pb2.RerankHotlistIssuesRequest(
870 hotlist_ref=common_pb2.HotlistRef(
871 name='RerankHotlistName',
872 owner=common_pb2.UserRef(user_id=self.user1.user_id)),
873 moved_refs=[common_pb2.IssueRef(
874 project_name='proj', local_id=2)],
875 target_ref=common_pb2.IssueRef(project_name='proj', local_id=4),
876 split_above=True)
877
878 mc = monorailcontext.MonorailContext(
879 self.services, cnxn=self.cnxn, requester=self.user1.email)
880 mc.LookupLoggedInUserPerms(self.project)
881 self.CallWrapped(self.features_svcr.RerankHotlistIssues, mc, request)
882
883 self.assertEqual(
884 [item.issue_id for item in hotlist.items],
885 [78901, 78903, 78902, 78904])
886
887 def testUpdateHotlistIssueNote(self):
888 hotlist = self.services.features.CreateHotlist(
889 self.cnxn, 'Hotlist-1', 'Summary', 'Description', owner_ids=[111],
890 editor_ids=[])
891 self.services.features.AddIssuesToHotlists(
892 self.cnxn,
893 [hotlist.hotlist_id], [(self.issue_1.issue_id, 111, 0, '')],
894 None, None, None)
895
896 request = features_pb2.UpdateHotlistIssueNoteRequest(
897 hotlist_ref=common_pb2.HotlistRef(
898 name='Hotlist-1',
899 owner=common_pb2.UserRef(user_id=111)),
900 issue_ref=common_pb2.IssueRef(
901 project_name='proj',
902 local_id=1),
903 note='Note')
904
905 mc = monorailcontext.MonorailContext(
906 self.services, cnxn=self.cnxn, requester='owner@example.com')
907 self.CallWrapped(self.features_svcr.UpdateHotlistIssueNote, mc, request)
908
909 self.assertEqual('Note', hotlist.items[0].note)
910
911 def testUpdateHotlistIssueNote_NotAllowed(self):
912 hotlist = self.services.features.CreateHotlist(
913 self.cnxn, 'Hotlist-1', 'Summary', 'Description', owner_ids=[222],
914 editor_ids=[])
915 self.services.features.AddIssuesToHotlists(
916 self.cnxn,
917 [hotlist.hotlist_id], [(self.issue_1.issue_id, 222, 0, '')],
918 None, None, None)
919
920 request = features_pb2.UpdateHotlistIssueNoteRequest(
921 hotlist_ref=common_pb2.HotlistRef(
922 name='Hotlist-1',
923 owner=common_pb2.UserRef(user_id=222)),
924 issue_ref=common_pb2.IssueRef(
925 project_name='proj',
926 local_id=1),
927 note='Note')
928
929 mc = monorailcontext.MonorailContext(
930 self.services, cnxn=self.cnxn, requester='owner@example.com')
931 with self.assertRaises(permissions.PermissionException):
932 self.CallWrapped(self.features_svcr.UpdateHotlistIssueNote, mc, request)
933
934 mc = monorailcontext.MonorailContext(
935 self.services, cnxn=self.cnxn, requester=self.user2.email)
936 mc.LookupLoggedInUserPerms(None)
937
938 mc = monorailcontext.MonorailContext(
939 self.services, cnxn=self.cnxn, requester=self.user2.email)
940 mc.LookupLoggedInUserPerms(None)
941
942 def testDeleteHotlist(self):
943 """Test we can delete a hotlist via the API."""
944 owner_ids = [self.user2.user_id]
945 editor_ids = []
946 hotlist = self.services.features.TestAddHotlist(
947 name='Hotlist-1', summary='summary', description='description',
948 owner_ids=owner_ids, editor_ids=editor_ids, hotlist_id=1235)
949 request = features_pb2.DeleteHotlistRequest(
950 hotlist_ref=common_pb2.HotlistRef(
951 name=hotlist.name,
952 owner=common_pb2.UserRef(user_id=self.user2.user_id)))
953
954 mc = monorailcontext.MonorailContext(
955 self.services, cnxn=self.cnxn, requester=self.user2.email)
956 mc.LookupLoggedInUserPerms(None)
957 self.CallWrapped(self.features_svcr.DeleteHotlist, mc, request)
958
959 self.assertTrue(
960 hotlist.hotlist_id in self.services.features.expunged_hotlist_ids)