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