blob: 170cf2eef5cbd417067e12effe37b0db9a62015d [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2020 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style
3# license that can be found in the LICENSE file or at
4# https://developers.google.com/open-source/licenses/bsd
5
6"""Tests for the hotlists servicer."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import unittest
12
13from google.protobuf import empty_pb2
14from google.protobuf import field_mask_pb2
15
16from api import resource_name_converters as rnc
17from api.v3 import hotlists_servicer
18from api.v3 import converters
19from api.v3.api_proto import hotlists_pb2
20from api.v3.api_proto import feature_objects_pb2
21from api.v3.api_proto import issue_objects_pb2
22from api.v3.api_proto import user_objects_pb2
23from framework import exceptions
24from framework import monorailcontext
25from framework import permissions
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020026from framework import sorting
Copybara854996b2021-09-07 19:36:02 +000027from features import features_constants
28from testing import fake
29from services import features_svc
30from services import service_manager
31
32
33class HotlistsServicerTest(unittest.TestCase):
34
35 def setUp(self):
36 self.cnxn = fake.MonorailConnection()
37 self.services = service_manager.Services(
38 features=fake.FeaturesService(),
39 issue=fake.IssueService(),
40 project=fake.ProjectService(),
41 config=fake.ConfigService(),
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020042 cache_manager=fake.CacheManager(),
Copybara854996b2021-09-07 19:36:02 +000043 user=fake.UserService(),
44 usergroup=fake.UserGroupService())
45 self.hotlists_svcr = hotlists_servicer.HotlistsServicer(
46 self.services, make_rate_limiter=False)
47 self.converter = None
48 self.PAST_TIME = 12345
49 self.user_1 = self.services.user.TestAddUser('user_111@example.com', 111)
50 self.user_2 = self.services.user.TestAddUser('user_222@example.com', 222)
51 self.user_3 = self.services.user.TestAddUser('user_333@example.com', 333)
52
53 user_ids = [self.user_1.user_id, self.user_2.user_id, self.user_3.user_id]
54 self.user_ids_to_name = rnc.ConvertUserNames(user_ids)
55
56 self.project_1 = self.services.project.TestAddProject(
57 'proj', project_id=789)
58
59 self.issue_1 = fake.MakeTestIssue(
60 self.project_1.project_id, 1, 'sum', 'New', 111,
61 project_name=self.project_1.project_name)
62 self.issue_2 = fake.MakeTestIssue(
63 self.project_1.project_id, 2, 'sum', 'New', 111,
64 project_name=self.project_1.project_name)
65 self.issue_3 = fake.MakeTestIssue(
66 self.project_1.project_id, 3, 'sum', 'New', 111,
67 project_name=self.project_1.project_name)
68 self.issue_4 = fake.MakeTestIssue(
69 self.project_1.project_id, 4, 'sum', 'New', 111,
70 project_name=self.project_1.project_name)
71 self.issue_5 = fake.MakeTestIssue(
72 self.project_1.project_id, 5, 'sum', 'New', 111,
73 project_name=self.project_1.project_name)
74 self.issue_6 = fake.MakeTestIssue(
75 self.project_1.project_id, 6, 'sum', 'New', 111,
76 project_name=self.project_1.project_name)
77 self.services.issue.TestAddIssue(self.issue_1)
78 self.services.issue.TestAddIssue(self.issue_2)
79 self.services.issue.TestAddIssue(self.issue_3)
80 self.services.issue.TestAddIssue(self.issue_4)
81 self.services.issue.TestAddIssue(self.issue_5)
82 self.services.issue.TestAddIssue(self.issue_6)
83 issue_ids = [
84 self.issue_1.issue_id, self.issue_2.issue_id, self.issue_3.issue_id,
85 self.issue_4.issue_id, self.issue_5.issue_id, self.issue_6.issue_id
86 ]
87 self.issue_ids_to_name = rnc.ConvertIssueNames(
88 self.cnxn, issue_ids, self.services)
89
90 hotlist_items = [
91 (
92 self.issue_4.issue_id, 31, self.user_3.user_id, self.PAST_TIME,
93 'note5'),
94 (
95 self.issue_3.issue_id, 21, self.user_1.user_id, self.PAST_TIME,
96 'note1'),
97 (
98 self.issue_2.issue_id, 11, self.user_2.user_id, self.PAST_TIME,
99 'note2'),
100 (
101 self.issue_1.issue_id, 1, self.user_1.user_id, self.PAST_TIME,
102 'note4')
103 ]
104 self.hotlist_1 = self.services.features.TestAddHotlist(
105 'HotlistName',
106 summary='summary',
107 description='description',
108 owner_ids=[self.user_1.user_id],
109 editor_ids=[self.user_2.user_id],
110 hotlist_item_fields=hotlist_items,
111 default_col_spec='',
112 is_private=True)
113 self.hotlist_resource_name = rnc.ConvertHotlistName(
114 self.hotlist_1.hotlist_id)
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200115 sorting.InitializeArtValues(self.services)
Copybara854996b2021-09-07 19:36:02 +0000116
117 def CallWrapped(self, wrapped_handler, mc, *args, **kwargs):
118 self.converter = converters.Converter(mc, self.services)
119 self.hotlists_svcr.converter = self.converter
120 return wrapped_handler.wrapped(self.hotlists_svcr, mc, *args, **kwargs)
121
122 # TODO(crbug/monorail/7104): Add page_token tests when implemented.
123 def testListHotlistItems(self):
124 """We can list a Hotlist's HotlistItems."""
125 request = hotlists_pb2.ListHotlistItemsRequest(
126 parent=self.hotlist_resource_name, page_size=2, order_by='note,stars')
127 mc = monorailcontext.MonorailContext(
128 self.services, cnxn=self.cnxn, requester=self.user_1.email)
129 mc.LookupLoggedInUserPerms(None)
130 response = self.CallWrapped(
131 self.hotlists_svcr.ListHotlistItems, mc, request)
132 expected_items = self.converter.ConvertHotlistItems(
133 self.hotlist_1.hotlist_id,
134 [self.hotlist_1.items[1], self.hotlist_1.items[2]])
135 self.assertEqual(
136 response, hotlists_pb2.ListHotlistItemsResponse(items=expected_items))
137
138 def testListHotlistItems_Empty(self):
139 """We can return a response if the Hotlist has no items"""
140 empty_hotlist = self.services.features.TestAddHotlist(
141 'Empty',
142 owner_ids=[self.user_1.user_id],
143 editor_ids=[self.user_2.user_id],
144 hotlist_item_fields=[])
145 hotlist_resource_name = rnc.ConvertHotlistName(empty_hotlist.hotlist_id)
146 request = hotlists_pb2.ListHotlistItemsRequest(parent=hotlist_resource_name)
147 mc = monorailcontext.MonorailContext(
148 self.services, cnxn=self.cnxn, requester=self.user_1.email)
149 mc.LookupLoggedInUserPerms(None)
150 response = self.CallWrapped(
151 self.hotlists_svcr.ListHotlistItems, mc, request)
152 self.assertEqual(response, hotlists_pb2.ListHotlistItemsResponse(items=[]))
153
154 def testListHotlistItems_InvalidPageSize(self):
155 """We raise an exception if `page_size` is negative."""
156 request = hotlists_pb2.ListHotlistItemsRequest(
157 parent=self.hotlist_resource_name, page_size=-1)
158 mc = monorailcontext.MonorailContext(
159 self.services, cnxn=self.cnxn, requester=self.user_1.email)
160 with self.assertRaises(exceptions.InputException):
161 self.CallWrapped(self.hotlists_svcr.ListHotlistItems, mc, request)
162
163 def testListHotlistItems_DefaultPageSize(self):
164 """We use our default page size when no `page_size` is given."""
165 request = hotlists_pb2.ListHotlistItemsRequest(
166 parent=self.hotlist_resource_name)
167 mc = monorailcontext.MonorailContext(
168 self.services, cnxn=self.cnxn, requester=self.user_1.email)
169 mc.LookupLoggedInUserPerms(None)
170 response = self.CallWrapped(
171 self.hotlists_svcr.ListHotlistItems, mc, request)
172 self.assertEqual(
173 len(response.items),
174 min(
175 features_constants.DEFAULT_RESULTS_PER_PAGE,
176 len(self.hotlist_1.items)))
177
178 def testRerankHotlistItems(self):
179 """We can rerank a Hotlist."""
180 item_names_dict = rnc.ConvertHotlistItemNames(
181 self.cnxn, self.hotlist_1.hotlist_id,
182 [item.issue_id for item in self.hotlist_1.items], self.services)
183 request = hotlists_pb2.RerankHotlistItemsRequest(
184 name=self.hotlist_resource_name,
185 hotlist_items=[
186 item_names_dict[self.issue_4.issue_id],
187 item_names_dict[self.issue_3.issue_id]
188 ],
189 target_position=0)
190
191 mc = monorailcontext.MonorailContext(
192 self.services, cnxn=self.cnxn, requester=self.user_1.email)
193 mc.LookupLoggedInUserPerms(None)
194 self.CallWrapped(self.hotlists_svcr.RerankHotlistItems, mc, request)
195 updated_hotlist = self.services.features.GetHotlist(
196 self.cnxn, self.hotlist_1.hotlist_id)
197 self.assertEqual(
198 [item.issue_id for item in updated_hotlist.items],
199 [self.issue_4.issue_id, self.issue_3.issue_id,
200 self.issue_1.issue_id, self.issue_2.issue_id])
201
202 def testRemoveHotlistItems(self):
203 """We can remove items from a Hotlist."""
204 issue_1_name = self.issue_ids_to_name[self.issue_1.issue_id]
205 issue_2_name = self.issue_ids_to_name[self.issue_2.issue_id]
206 request = hotlists_pb2.RemoveHotlistItemsRequest(
207 parent=self.hotlist_resource_name, issues=[issue_1_name, issue_2_name])
208
209 mc = monorailcontext.MonorailContext(
210 self.services, cnxn=self.cnxn, requester=self.user_1.email)
211 mc.LookupLoggedInUserPerms(None)
212 self.CallWrapped(self.hotlists_svcr.RemoveHotlistItems, mc, request)
213 updated_hotlist = self.services.features.GetHotlist(
214 self.cnxn, self.hotlist_1.hotlist_id)
215 # The hotlist used to have 4 items and we've removed two.
216 self.assertEqual(len(updated_hotlist.items), 2)
217
218 def testAddHotlistItems(self):
219 """We can add items to a Hotlist."""
220 issue_5_name = self.issue_ids_to_name[self.issue_5.issue_id]
221 issue_6_name = self.issue_ids_to_name[self.issue_6.issue_id]
222 request = hotlists_pb2.AddHotlistItemsRequest(
223 parent=self.hotlist_resource_name, issues=[issue_5_name, issue_6_name])
224
225 mc = monorailcontext.MonorailContext(
226 self.services, cnxn=self.cnxn, requester=self.user_1.email)
227 mc.LookupLoggedInUserPerms(None)
228 self.CallWrapped(self.hotlists_svcr.AddHotlistItems, mc, request)
229 updated_hotlist = self.services.features.GetHotlist(
230 self.cnxn, self.hotlist_1.hotlist_id)
231 # The hotlist used to have 4 items and we've added two.
232 self.assertEqual(len(updated_hotlist.items), 6)
233
234 def testRemoveHotlistEditors(self):
235 """We can remove editors from a Hotlist."""
236 user_2_name = self.user_ids_to_name[self.user_2.user_id]
237 request = hotlists_pb2.RemoveHotlistEditorsRequest(
238 name=self.hotlist_resource_name, editors=[user_2_name])
239
240 mc = monorailcontext.MonorailContext(
241 self.services, cnxn=self.cnxn, requester=self.user_1.email)
242 mc.LookupLoggedInUserPerms(None)
243 self.CallWrapped(self.hotlists_svcr.RemoveHotlistEditors, mc, request)
244 updated_hotlist = self.services.features.GetHotlist(
245 self.cnxn, self.hotlist_1.hotlist_id)
246 # User 2 was the only editor in the hotlist, and we removed them.
247 self.assertEqual(len(updated_hotlist.editor_ids), 0)
248
249 def testGetHotlist(self):
250 """We can get a Hotlist."""
251 request = hotlists_pb2.GetHotlistRequest(name=self.hotlist_resource_name)
252
253 mc = monorailcontext.MonorailContext(
254 self.services, cnxn=self.cnxn, requester=self.user_1.email)
255 mc.LookupLoggedInUserPerms(None)
256 api_hotlist = self.CallWrapped(self.hotlists_svcr.GetHotlist, mc, request)
257 self.assertEqual(api_hotlist, self.converter.ConvertHotlist(self.hotlist_1))
258
259 def testGatherHotlistsForUser(self):
260 """We can get all visible hotlists of a user."""
261 request = hotlists_pb2.GatherHotlistsForUserRequest(
262 user=self.user_ids_to_name[self.user_2.user_id])
263
264 mc = monorailcontext.MonorailContext(
265 self.services, cnxn=self.cnxn, requester=self.user_1.email)
266 mc.LookupLoggedInUserPerms(None)
267 response = self.CallWrapped(
268 self.hotlists_svcr.GatherHotlistsForUser, mc, request)
269
270 user_names_by_id = rnc.ConvertUserNames(
271 [self.user_2.user_id, self.user_1.user_id])
272 expected_api_hotlists = [
273 feature_objects_pb2.Hotlist(
274 name=self.hotlist_resource_name,
275 display_name='HotlistName',
276 summary='summary',
277 description='description',
278 hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
279 'PRIVATE'),
280 owner=user_names_by_id[self.user_1.user_id],
281 editors=[user_names_by_id[self.user_2.user_id]])
282 ]
283 self.assertEqual(
284 response,
285 hotlists_pb2.GatherHotlistsForUserResponse(
286 hotlists=expected_api_hotlists))
287
288 def testUpdateHotlist_AllFields(self):
289 """We can update a Hotlist."""
290 request = hotlists_pb2.UpdateHotlistRequest(
291 update_mask=field_mask_pb2.FieldMask(
292 paths=[
293 'summary',
294 'description',
295 'default_columns',
296 'hotlist_privacy',
297 'display_name',
298 'owner',
299 'editors',
300 ]),
301 hotlist=feature_objects_pb2.Hotlist(
302 name=self.hotlist_resource_name,
303 display_name='newName',
304 summary='new summary',
305 description='new description',
306 default_columns=[
307 issue_objects_pb2.IssuesListColumn(column='new-chicken-egg')
308 ],
309 hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
310 'PUBLIC'),
311 owner=self.user_ids_to_name[self.user_2.user_id],
312 editors=[self.user_ids_to_name[self.user_3.user_id]]))
313 mc = monorailcontext.MonorailContext(
314 self.services, cnxn=self.cnxn, requester=self.user_1.email)
315 mc.LookupLoggedInUserPerms(None)
316 api_hotlist = self.CallWrapped(
317 self.hotlists_svcr.UpdateHotlist, mc, request)
318 user_names_by_id = rnc.ConvertUserNames(
319 [self.user_3.user_id, self.user_2.user_id, self.user_1.user_id])
320 expected_hotlist = feature_objects_pb2.Hotlist(
321 name=self.hotlist_resource_name,
322 display_name='newName',
323 summary='new summary',
324 description='new description',
325 default_columns=[
326 issue_objects_pb2.IssuesListColumn(column='new-chicken-egg')
327 ],
328 hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
329 'PUBLIC'),
330 owner=user_names_by_id[self.user_2.user_id],
331 editors=[
332 user_names_by_id[self.user_2.user_id],
333 user_names_by_id[self.user_3.user_id]
334 ])
335 self.assertEqual(api_hotlist, expected_hotlist)
336
337 def testUpdateHotlist_OneField(self):
338 request = hotlists_pb2.UpdateHotlistRequest(
339 update_mask=field_mask_pb2.FieldMask(paths=['summary']),
340 hotlist=feature_objects_pb2.Hotlist(
341 name=self.hotlist_resource_name,
342 display_name='newName',
343 summary='new summary',
344 description='new description',
345 default_columns=[
346 issue_objects_pb2.IssuesListColumn(column='new-chicken-egg')
347 ],
348 hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
349 'PUBLIC')))
350 mc = monorailcontext.MonorailContext(
351 self.services, cnxn=self.cnxn, requester=self.user_1.email)
352 mc.LookupLoggedInUserPerms(None)
353 api_hotlist = self.CallWrapped(
354 self.hotlists_svcr.UpdateHotlist, mc, request)
355 user_names_by_id = rnc.ConvertUserNames(
356 [self.user_2.user_id, self.user_1.user_id])
357 expected_hotlist = feature_objects_pb2.Hotlist(
358 name=self.hotlist_resource_name,
359 display_name='HotlistName',
360 summary='new summary',
361 description='description',
362 default_columns=[],
363 hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
364 'PRIVATE'),
365 owner=user_names_by_id[self.user_1.user_id],
366 editors=[user_names_by_id[self.user_2.user_id]])
367 self.assertEqual(api_hotlist, expected_hotlist)
368
369 def testUpdateHotlist_EmptyFieldMask(self):
370 request = hotlists_pb2.UpdateHotlistRequest(
371 hotlist=feature_objects_pb2.Hotlist(summary='new'))
372 mc = monorailcontext.MonorailContext(
373 self.services, cnxn=self.cnxn, requester=self.user_1.email)
374 mc.LookupLoggedInUserPerms(None)
375 with self.assertRaises(exceptions.InputException):
376 self.CallWrapped(self.hotlists_svcr.UpdateHotlist, mc, request)
377
378 def testUpdateHotlist_EmptyHotlist(self):
379 request = hotlists_pb2.UpdateHotlistRequest(
380 update_mask=field_mask_pb2.FieldMask(paths=['summary']))
381 mc = monorailcontext.MonorailContext(
382 self.services, cnxn=self.cnxn, requester=self.user_1.email)
383 mc.LookupLoggedInUserPerms(None)
384 with self.assertRaises(exceptions.InputException):
385 self.CallWrapped(self.hotlists_svcr.UpdateHotlist, mc, request)
386
387 def testDeleteHotlist(self):
388 """We can delete a Hotlist."""
389 request = hotlists_pb2.GetHotlistRequest(name=self.hotlist_resource_name)
390
391 mc = monorailcontext.MonorailContext(
392 self.services, cnxn=self.cnxn, requester=self.user_1.email)
393 mc.LookupLoggedInUserPerms(None)
394 api_response = self.CallWrapped(
395 self.hotlists_svcr.DeleteHotlist, mc, request)
396 self.assertEqual(api_response, empty_pb2.Empty())
397
398 with self.assertRaises(features_svc.NoSuchHotlistException):
399 self.services.features.GetHotlist(
400 self.cnxn, self.hotlist_1.hotlist_id)