blob: fcd05460b2e2418676fc933e04d36521bf4613df [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 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"""Unit tests for features_svc module."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
10import logging
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020011try:
12 from mox3 import mox
13except ImportError:
14 import mox
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010015import six
Copybara854996b2021-09-07 19:36:02 +000016import time
17import unittest
18import mock
19
20from google.appengine.api import memcache
21from google.appengine.ext import testbed
22
23import settings
24
25from features import filterrules_helpers
26from features import features_constants
27from framework import exceptions
28from framework import framework_constants
29from framework import sql
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010030from mrproto import tracker_pb2
31from mrproto import features_pb2
Copybara854996b2021-09-07 19:36:02 +000032from services import chart_svc
33from services import features_svc
34from services import star_svc
35from services import user_svc
36from testing import fake
37from tracker import tracker_bizobj
38from tracker import tracker_constants
39
40
41# NOTE: we are in the process of moving away from mox towards mock.
42# This file is a mix of both. All new tests or big test updates should make
43# use of the mock package.
44def MakeFeaturesService(cache_manager, my_mox):
45 features_service = features_svc.FeaturesService(cache_manager,
46 fake.ConfigService())
47 features_service.hotlist_tbl = my_mox.CreateMock(sql.SQLTableManager)
48 features_service.hotlist2issue_tbl = my_mox.CreateMock(sql.SQLTableManager)
49 features_service.hotlist2user_tbl = my_mox.CreateMock(sql.SQLTableManager)
50 return features_service
51
52
53class HotlistTwoLevelCacheTest(unittest.TestCase):
54
55 def setUp(self):
56 self.testbed = testbed.Testbed()
57 self.testbed.activate()
58 self.testbed.init_memcache_stub()
59
60 self.mox = mox.Mox()
61 self.cnxn = self.mox.CreateMock(sql.MonorailConnection)
62 self.cache_manager = fake.CacheManager()
63 self.features_service = MakeFeaturesService(self.cache_manager, self.mox)
64
65 def tearDown(self):
66 self.testbed.deactivate()
67
68 def testDeserializeHotlists(self):
69 hotlist_rows = [
70 (123, 'hot1', 'test hot 1', 'test hotlist', False, ''),
71 (234, 'hot2', 'test hot 2', 'test hotlist', False, '')]
72
73 ts = 20021111111111
74 issue_rows = [
75 (123, 567, 10, 111, ts, ''), (123, 678, 9, 111, ts, ''),
76 (234, 567, 0, 111, ts, '')]
77 role_rows = [
78 (123, 111, 'owner'), (123, 444, 'owner'),
79 (123, 222, 'editor'),
80 (123, 333, 'follower'),
81 (234, 111, 'owner')]
82 hotlist_dict = self.features_service.hotlist_2lc._DeserializeHotlists(
83 hotlist_rows, issue_rows, role_rows)
84
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010085 six.assertCountEqual(self, [123, 234], list(hotlist_dict.keys()))
Copybara854996b2021-09-07 19:36:02 +000086 self.assertEqual(123, hotlist_dict[123].hotlist_id)
87 self.assertEqual('hot1', hotlist_dict[123].name)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010088 six.assertCountEqual(self, [111, 444], hotlist_dict[123].owner_ids)
89 six.assertCountEqual(self, [222], hotlist_dict[123].editor_ids)
90 six.assertCountEqual(self, [333], hotlist_dict[123].follower_ids)
Copybara854996b2021-09-07 19:36:02 +000091 self.assertEqual(234, hotlist_dict[234].hotlist_id)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010092 six.assertCountEqual(self, [111], hotlist_dict[234].owner_ids)
Copybara854996b2021-09-07 19:36:02 +000093
94
95class HotlistIDTwoLevelCache(unittest.TestCase):
96
97 def setUp(self):
98 self.testbed = testbed.Testbed()
99 self.testbed.activate()
100 self.testbed.init_memcache_stub()
101
102 self.mox = mox.Mox()
103 self.cnxn = self.mox.CreateMock(sql.MonorailConnection)
104 self.cache_manager = fake.CacheManager()
105 self.features_service = MakeFeaturesService(self.cache_manager, self.mox)
106 self.hotlist_id_2lc = self.features_service.hotlist_id_2lc
107
108 def tearDown(self):
109 memcache.flush_all()
110 self.testbed.deactivate()
111 self.mox.UnsetStubs()
112 self.mox.ResetAll()
113
114 def testGetAll(self):
115 cached_keys = [('name1', 111), ('name2', 222)]
116 self.hotlist_id_2lc.CacheItem(cached_keys[0], 121)
117 self.hotlist_id_2lc.CacheItem(cached_keys[1], 122)
118
119 # Set up DB query mocks.
120 # Test that a ('name1', 222) or ('name3', 333) hotlist
121 # does not get returned by GetAll even though these hotlists
122 # exist and are returned by the DB queries.
123 from_db_keys = [
124 ('name1', 333), ('name3', 222), ('name3', 555)]
125 self.features_service.hotlist2user_tbl.Select = mock.Mock(return_value=[
126 (123, 333), # name1 hotlist
127 (124, 222), # name3 hotlist
128 (125, 222), # name1 hotlist, should be ignored
129 (126, 333), # name3 hotlist, should be ignored
130 (127, 555), # wrongname hotlist, should be ignored
131 ])
132 self.features_service.hotlist_tbl.Select = mock.Mock(
133 return_value=[(123, 'Name1'), (124, 'Name3'),
134 (125, 'Name1'), (126, 'Name3')])
135
136 hit, misses = self.hotlist_id_2lc.GetAll(
137 self.cnxn, cached_keys + from_db_keys)
138
139 # Assertions
140 self.features_service.hotlist2user_tbl.Select.assert_called_once_with(
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100141 self.cnxn, cols=['hotlist_id', 'user_id'], user_id=[222, 333, 555],
Copybara854996b2021-09-07 19:36:02 +0000142 role_name='owner')
143 hotlist_ids = [123, 124, 125, 126, 127]
144 self.features_service.hotlist_tbl.Select.assert_called_once_with(
145 self.cnxn, cols=['id', 'name'], id=hotlist_ids, is_deleted=False,
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100146 where=[('LOWER(name) IN (%s,%s)', ['name1', 'name3'])])
Copybara854996b2021-09-07 19:36:02 +0000147
148 self.assertEqual(hit,{
149 ('name1', 111): 121,
150 ('name2', 222): 122,
151 ('name1', 333): 123,
152 ('name3', 222): 124})
153 self.assertEqual(from_db_keys[-1:], misses)
154
155
156class FeaturesServiceTest(unittest.TestCase):
157
158 def MakeMockTable(self):
159 return self.mox.CreateMock(sql.SQLTableManager)
160
161 def setUp(self):
162 self.testbed = testbed.Testbed()
163 self.testbed.activate()
164 self.testbed.init_memcache_stub()
165
166 self.mox = mox.Mox()
167 self.cnxn = self.mox.CreateMock(sql.MonorailConnection)
168 self.cache_manager = fake.CacheManager()
169 self.config_service = fake.ConfigService()
170
171 self.features_service = features_svc.FeaturesService(self.cache_manager,
172 self.config_service)
173 self.issue_service = fake.IssueService()
174 self.chart_service = self.mox.CreateMock(chart_svc.ChartService)
175
176 for table_var in [
177 'user2savedquery_tbl', 'quickedithistory_tbl',
178 'quickeditmostrecent_tbl', 'savedquery_tbl',
179 'savedqueryexecutesinproject_tbl', 'project2savedquery_tbl',
180 'filterrule_tbl', 'hotlist_tbl', 'hotlist2issue_tbl',
181 'hotlist2user_tbl']:
182 setattr(self.features_service, table_var, self.MakeMockTable())
183
184 def tearDown(self):
185 memcache.flush_all()
186 self.testbed.deactivate()
187 self.mox.UnsetStubs()
188 self.mox.ResetAll()
189
190 ### quickedit command history
191
192 def testGetRecentCommands(self):
193 self.features_service.quickedithistory_tbl.Select(
194 self.cnxn, cols=['slot_num', 'command', 'comment'],
195 user_id=1, project_id=12345).AndReturn(
196 [(1, 'status=New', 'Brand new issue')])
197 self.features_service.quickeditmostrecent_tbl.SelectValue(
198 self.cnxn, 'slot_num', default=1, user_id=1, project_id=12345
199 ).AndReturn(1)
200 self.mox.ReplayAll()
201 slots, recent_slot_num = self.features_service.GetRecentCommands(
202 self.cnxn, 1, 12345)
203 self.mox.VerifyAll()
204
205 self.assertEqual(1, recent_slot_num)
206 self.assertEqual(
207 len(tracker_constants.DEFAULT_RECENT_COMMANDS), len(slots))
208 self.assertEqual('status=New', slots[0][1])
209
210 def testStoreRecentCommand(self):
211 self.features_service.quickedithistory_tbl.InsertRow(
212 self.cnxn, replace=True, user_id=1, project_id=12345,
213 slot_num=1, command='status=New', comment='Brand new issue')
214 self.features_service.quickeditmostrecent_tbl.InsertRow(
215 self.cnxn, replace=True, user_id=1, project_id=12345,
216 slot_num=1)
217 self.mox.ReplayAll()
218 self.features_service.StoreRecentCommand(
219 self.cnxn, 1, 12345, 1, 'status=New', 'Brand new issue')
220 self.mox.VerifyAll()
221
222 def testExpungeQuickEditHistory(self):
223 self.features_service.quickeditmostrecent_tbl.Delete(
224 self.cnxn, project_id=12345)
225 self.features_service.quickedithistory_tbl.Delete(
226 self.cnxn, project_id=12345)
227 self.mox.ReplayAll()
228 self.features_service.ExpungeQuickEditHistory(
229 self.cnxn, 12345)
230 self.mox.VerifyAll()
231
232 def testExpungeQuickEditsByUsers(self):
233 user_ids = [333, 555, 777]
234 commit = False
235
236 self.features_service.quickeditmostrecent_tbl.Delete = mock.Mock()
237 self.features_service.quickedithistory_tbl.Delete = mock.Mock()
238
239 self.features_service.ExpungeQuickEditsByUsers(
240 self.cnxn, user_ids, limit=50)
241
242 self.features_service.quickeditmostrecent_tbl.Delete.\
243assert_called_once_with(self.cnxn, user_id=user_ids, commit=commit, limit=50)
244 self.features_service.quickedithistory_tbl.Delete.\
245assert_called_once_with(self.cnxn, user_id=user_ids, commit=commit, limit=50)
246
247 ### Saved User and Project Queries
248
249 def testGetSavedQuery_Valid(self):
250 self.features_service.savedquery_tbl.Select(
251 self.cnxn, cols=features_svc.SAVEDQUERY_COLS, id=[1]).AndReturn(
252 [(1, 'query1', 100, 'owner:me')])
253 self.features_service.savedqueryexecutesinproject_tbl.Select(
254 self.cnxn, cols=features_svc.SAVEDQUERYEXECUTESINPROJECT_COLS,
255 query_id=[1]).AndReturn([(1, 12345)])
256 self.mox.ReplayAll()
257 saved_query = self.features_service.GetSavedQuery(
258 self.cnxn, 1)
259 self.mox.VerifyAll()
260 self.assertEqual(1, saved_query.query_id)
261 self.assertEqual('query1', saved_query.name)
262 self.assertEqual(100, saved_query.base_query_id)
263 self.assertEqual('owner:me', saved_query.query)
264 self.assertEqual([12345], saved_query.executes_in_project_ids)
265
266 def testGetSavedQuery_Invalid(self):
267 self.features_service.savedquery_tbl.Select(
268 self.cnxn, cols=features_svc.SAVEDQUERY_COLS, id=[99]).AndReturn([])
269 self.features_service.savedqueryexecutesinproject_tbl.Select(
270 self.cnxn, cols=features_svc.SAVEDQUERYEXECUTESINPROJECT_COLS,
271 query_id=[99]).AndReturn([])
272 self.mox.ReplayAll()
273 saved_query = self.features_service.GetSavedQuery(
274 self.cnxn, 99)
275 self.mox.VerifyAll()
276 self.assertIsNone(saved_query)
277
278 def SetUpUsersSavedQueries(self, has_query_id=True):
279 query = tracker_bizobj.MakeSavedQuery(1, 'query1', 100, 'owner:me')
280 self.features_service.saved_query_cache.CacheItem(1, [query])
281
282 if has_query_id:
283 self.features_service.user2savedquery_tbl.Select(
284 self.cnxn,
285 cols=features_svc.SAVEDQUERY_COLS + ['user_id', 'subscription_mode'],
286 left_joins=[('SavedQuery ON query_id = id', [])],
287 order_by=[('rank', [])],
288 user_id=[2]).AndReturn(
289 [(2, 'query2', 100, 'status:New', 2, 'Sub_Mode')])
290 self.features_service.savedqueryexecutesinproject_tbl.Select(
291 self.cnxn,
292 cols=features_svc.SAVEDQUERYEXECUTESINPROJECT_COLS,
293 query_id=set([2])).AndReturn([(2, 12345)])
294 else:
295 self.features_service.user2savedquery_tbl.Select(
296 self.cnxn,
297 cols=features_svc.SAVEDQUERY_COLS + ['user_id', 'subscription_mode'],
298 left_joins=[('SavedQuery ON query_id = id', [])],
299 order_by=[('rank', [])],
300 user_id=[2]).AndReturn([])
301
302 def testGetUsersSavedQueriesDict(self):
303 self.SetUpUsersSavedQueries()
304 self.mox.ReplayAll()
305 results_dict = self.features_service._GetUsersSavedQueriesDict(
306 self.cnxn, [1, 2])
307 self.mox.VerifyAll()
308 self.assertIn(1, results_dict)
309 self.assertIn(2, results_dict)
310
311 def testGetUsersSavedQueriesDictWithoutSavedQueries(self):
312 self.SetUpUsersSavedQueries(False)
313 self.mox.ReplayAll()
314 results_dict = self.features_service._GetUsersSavedQueriesDict(
315 self.cnxn, [1, 2])
316 self.mox.VerifyAll()
317 self.assertIn(1, results_dict)
318 self.assertNotIn(2, results_dict)
319
320 def testGetSavedQueriesByUserID(self):
321 self.SetUpUsersSavedQueries()
322 self.mox.ReplayAll()
323 saved_queries = self.features_service.GetSavedQueriesByUserID(
324 self.cnxn, 2)
325 self.mox.VerifyAll()
326 self.assertEqual(1, len(saved_queries))
327 self.assertEqual(2, saved_queries[0].query_id)
328
329 def SetUpCannedQueriesForProjects(self, project_ids):
330 query = tracker_bizobj.MakeSavedQuery(
331 2, 'project-query-2', 110, 'owner:goose@chaos.honk')
332 self.features_service.canned_query_cache.CacheItem(12346, [query])
333 self.features_service.canned_query_cache.CacheAll = mock.Mock()
334 self.features_service.project2savedquery_tbl.Select(
335 self.cnxn, cols=['project_id'] + features_svc.SAVEDQUERY_COLS,
336 left_joins=[('SavedQuery ON query_id = id', [])],
337 order_by=[('rank', [])], project_id=project_ids).AndReturn(
338 [(12345, 1, 'query1', 100, 'owner:me')])
339
340 def testGetCannedQueriesForProjects(self):
341 project_ids = [12345, 12346]
342 self.SetUpCannedQueriesForProjects(project_ids)
343 self.mox.ReplayAll()
344 results_dict = self.features_service.GetCannedQueriesForProjects(
345 self.cnxn, project_ids)
346 self.mox.VerifyAll()
347 self.assertIn(12345, results_dict)
348 self.assertIn(12346, results_dict)
349 self.features_service.canned_query_cache.CacheAll.assert_called_once_with(
350 results_dict)
351
352 def testGetCannedQueriesByProjectID(self):
353 project_id= 12345
354 self.SetUpCannedQueriesForProjects([project_id])
355 self.mox.ReplayAll()
356 result = self.features_service.GetCannedQueriesByProjectID(
357 self.cnxn, project_id)
358 self.mox.VerifyAll()
359 self.assertEqual(1, len(result))
360 self.assertEqual(1, result[0].query_id)
361
362 def SetUpUpdateSavedQueries(self, commit=True):
363 query1 = tracker_bizobj.MakeSavedQuery(1, 'query1', 100, 'owner:me')
364 query2 = tracker_bizobj.MakeSavedQuery(None, 'query2', 100, 'status:New')
365 saved_queries = [query1, query2]
366 savedquery_rows = [
367 (sq.query_id or None, sq.name, sq.base_query_id, sq.query)
368 for sq in saved_queries]
369 self.features_service.savedquery_tbl.Delete(
370 self.cnxn, id=[1], commit=commit)
371 self.features_service.savedquery_tbl.InsertRows(
372 self.cnxn, features_svc.SAVEDQUERY_COLS, savedquery_rows, commit=commit,
373 return_generated_ids=True).AndReturn([11, 12])
374 return saved_queries
375
376 def testUpdateSavedQueries(self):
377 saved_queries = self.SetUpUpdateSavedQueries()
378 self.mox.ReplayAll()
379 self.features_service._UpdateSavedQueries(
380 self.cnxn, saved_queries, True)
381 self.mox.VerifyAll()
382
383 def testUpdateCannedQueries(self):
384 self.features_service.project2savedquery_tbl.Delete(
385 self.cnxn, project_id=12345, commit=False)
386 canned_queries = self.SetUpUpdateSavedQueries(False)
387 project2savedquery_rows = [(12345, 0, 1), (12345, 1, 12)]
388 self.features_service.project2savedquery_tbl.InsertRows(
389 self.cnxn, features_svc.PROJECT2SAVEDQUERY_COLS,
390 project2savedquery_rows, commit=False)
391 self.features_service.canned_query_cache.Invalidate = mock.Mock()
392 self.cnxn.Commit()
393 self.mox.ReplayAll()
394 self.features_service.UpdateCannedQueries(
395 self.cnxn, 12345, canned_queries)
396 self.mox.VerifyAll()
397 self.features_service.canned_query_cache.Invalidate.assert_called_once_with(
398 self.cnxn, 12345)
399
400 def testUpdateUserSavedQueries(self):
401 saved_queries = self.SetUpUpdateSavedQueries(False)
402 self.features_service.savedqueryexecutesinproject_tbl.Delete(
403 self.cnxn, query_id=[1], commit=False)
404 self.features_service.user2savedquery_tbl.Delete(
405 self.cnxn, user_id=1, commit=False)
406 user2savedquery_rows = [
407 (1, 0, 1, 'noemail'), (1, 1, 12, 'noemail')]
408 self.features_service.user2savedquery_tbl.InsertRows(
409 self.cnxn, features_svc.USER2SAVEDQUERY_COLS,
410 user2savedquery_rows, commit=False)
411 self.features_service.savedqueryexecutesinproject_tbl.InsertRows(
412 self.cnxn, features_svc.SAVEDQUERYEXECUTESINPROJECT_COLS, [],
413 commit=False)
414 self.cnxn.Commit()
415 self.mox.ReplayAll()
416 self.features_service.UpdateUserSavedQueries(
417 self.cnxn, 1, saved_queries)
418 self.mox.VerifyAll()
419
420 ### Subscriptions
421
422 def testGetSubscriptionsInProjects(self):
423 sqeip_join_str = (
424 'SavedQueryExecutesInProject ON '
425 'SavedQueryExecutesInProject.query_id = User2SavedQuery.query_id')
426 user_join_str = (
427 'User ON '
428 'User.user_id = User2SavedQuery.user_id')
429 now = 1519418530
430 self.mox.StubOutWithMock(time, 'time')
431 time.time().MultipleTimes().AndReturn(now)
432 absence_threshold = now - settings.subscription_timeout_secs
433 where = [
434 ('(User.banned IS NULL OR User.banned = %s)', ['']),
435 ('User.last_visit_timestamp >= %s', [absence_threshold]),
436 ('(User.email_bounce_timestamp IS NULL OR '
437 'User.email_bounce_timestamp = %s)', [0]),
438 ]
439 self.features_service.user2savedquery_tbl.Select(
440 self.cnxn, cols=['User2SavedQuery.user_id'], distinct=True,
441 joins=[(sqeip_join_str, []), (user_join_str, [])],
442 subscription_mode='immediate', project_id=12345,
443 where=where).AndReturn(
444 [(1, 'asd'), (2, 'efg')])
445 self.SetUpUsersSavedQueries()
446 self.mox.ReplayAll()
447 result = self.features_service.GetSubscriptionsInProjects(
448 self.cnxn, 12345)
449 self.mox.VerifyAll()
450 self.assertIn(1, result)
451 self.assertIn(2, result)
452
453 def testExpungeSavedQueriesExecuteInProject(self):
454 self.features_service.savedqueryexecutesinproject_tbl.Delete(
455 self.cnxn, project_id=12345)
456 self.features_service.project2savedquery_tbl.Select(
457 self.cnxn, cols=['query_id'], project_id=12345).AndReturn(
458 [(1, 'asd'), (2, 'efg')])
459 self.features_service.project2savedquery_tbl.Delete(
460 self.cnxn, project_id=12345)
461 self.features_service.savedquery_tbl.Delete(
462 self.cnxn, id=[1, 2])
463 self.mox.ReplayAll()
464 self.features_service.ExpungeSavedQueriesExecuteInProject(
465 self.cnxn, 12345)
466 self.mox.VerifyAll()
467
468 def testExpungeSavedQueriesByUsers(self):
469 user_ids = [222, 444, 666]
470 commit = False
471
472 sv_rows = [(8,), (9,)]
473 self.features_service.user2savedquery_tbl.Select = mock.Mock(
474 return_value=sv_rows)
475 self.features_service.user2savedquery_tbl.Delete = mock.Mock()
476 self.features_service.savedqueryexecutesinproject_tbl.Delete = mock.Mock()
477 self.features_service.savedquery_tbl.Delete = mock.Mock()
478
479 self.features_service.ExpungeSavedQueriesByUsers(
480 self.cnxn, user_ids, limit=50)
481
482 self.features_service.user2savedquery_tbl.Select.assert_called_once_with(
483 self.cnxn, cols=['query_id'], user_id=user_ids, limit=50)
484 self.features_service.user2savedquery_tbl.Delete.assert_called_once_with(
485 self.cnxn, query_id=[8, 9], commit=commit)
486 self.features_service.savedqueryexecutesinproject_tbl.\
487Delete.assert_called_once_with(
488 self.cnxn, query_id=[8, 9], commit=commit)
489 self.features_service.savedquery_tbl.Delete.assert_called_once_with(
490 self.cnxn, id=[8, 9], commit=commit)
491
492
493 ### Filter Rules
494
495 def testDeserializeFilterRules(self):
496 filterrule_rows = [
497 (12345, 0, 'predicate1', 'default_status:New'),
498 (12345, 1, 'predicate2', 'default_owner_id:1 add_cc_id:2'),
499 ]
500 result_dict = self.features_service._DeserializeFilterRules(
501 filterrule_rows)
502 self.assertIn(12345, result_dict)
503 self.assertEqual(2, len(result_dict[12345]))
504 self.assertEqual('New', result_dict[12345][0].default_status)
505 self.assertEqual(1, result_dict[12345][1].default_owner_id)
506 self.assertEqual([2], result_dict[12345][1].add_cc_ids)
507
508 def testDeserializeRuleConsequence_Multiple(self):
509 consequence = ('default_status:New default_owner_id:1 add_cc_id:2'
510 ' add_label:label-1 add_label:label.2'
511 ' add_notify:admin@example.com')
512 (default_status, default_owner_id, add_cc_ids, add_labels,
513 add_notify, warning, error
514 ) = self.features_service._DeserializeRuleConsequence(
515 consequence)
516 self.assertEqual('New', default_status)
517 self.assertEqual(1, default_owner_id)
518 self.assertEqual([2], add_cc_ids)
519 self.assertEqual(['label-1', 'label.2'], add_labels)
520 self.assertEqual(['admin@example.com'], add_notify)
521 self.assertEqual(None, warning)
522 self.assertEqual(None, error)
523
524 def testDeserializeRuleConsequence_Warning(self):
525 consequence = ('warning:Do not use status:New if there is an owner')
526 (_status, _owner_id, _cc_ids, _labels, _notify,
527 warning, _error) = self.features_service._DeserializeRuleConsequence(
528 consequence)
529 self.assertEqual(
530 'Do not use status:New if there is an owner',
531 warning)
532
533 def testDeserializeRuleConsequence_Error(self):
534 consequence = ('error:Pri-0 issues require an owner')
535 (_status, _owner_id, _cc_ids, _labels, _notify,
536 _warning, error) = self.features_service._DeserializeRuleConsequence(
537 consequence)
538 self.assertEqual(
539 'Pri-0 issues require an owner',
540 error)
541
542 def SetUpGetFilterRulesByProjectIDs(self):
543 filterrule_rows = [
544 (12345, 0, 'predicate1', 'default_status:New'),
545 (12345, 1, 'predicate2', 'default_owner_id:1 add_cc_id:2'),
546 ]
547
548 self.features_service.filterrule_tbl.Select(
549 self.cnxn, cols=features_svc.FILTERRULE_COLS,
550 project_id=[12345]).AndReturn(filterrule_rows)
551
552 def testGetFilterRulesByProjectIDs(self):
553 self.SetUpGetFilterRulesByProjectIDs()
554 self.mox.ReplayAll()
555 result = self.features_service._GetFilterRulesByProjectIDs(
556 self.cnxn, [12345])
557 self.mox.VerifyAll()
558 self.assertIn(12345, result)
559 self.assertEqual(2, len(result[12345]))
560
561 def testGetFilterRules(self):
562 self.SetUpGetFilterRulesByProjectIDs()
563 self.mox.ReplayAll()
564 result = self.features_service.GetFilterRules(
565 self.cnxn, 12345)
566 self.mox.VerifyAll()
567 self.assertEqual(2, len(result))
568
569 def testSerializeRuleConsequence(self):
570 rule = filterrules_helpers.MakeRule(
571 'predicate', 'New', 1, [1, 2], ['label1', 'label2'], ['admin'])
572 result = self.features_service._SerializeRuleConsequence(rule)
573 self.assertEqual('add_label:label1 add_label:label2 default_status:New'
574 ' default_owner_id:1 add_cc_id:1 add_cc_id:2'
575 ' add_notify:admin', result)
576
577 def testUpdateFilterRules(self):
578 self.features_service.filterrule_tbl.Delete(self.cnxn, project_id=12345)
579 rows = [
580 (12345, 0, 'predicate1', 'add_label:label1 add_label:label2'
581 ' default_status:New default_owner_id:1'
582 ' add_cc_id:1 add_cc_id:2 add_notify:admin'),
583 (12345, 1, 'predicate2', 'add_label:label2 add_label:label3'
584 ' default_status:Fixed default_owner_id:2'
585 ' add_cc_id:1 add_cc_id:2 add_notify:admin2')
586 ]
587 self.features_service.filterrule_tbl.InsertRows(
588 self.cnxn, features_svc.FILTERRULE_COLS, rows)
589 rule1 = filterrules_helpers.MakeRule(
590 'predicate1', 'New', 1, [1, 2], ['label1', 'label2'], ['admin'])
591 rule2 = filterrules_helpers.MakeRule(
592 'predicate2', 'Fixed', 2, [1, 2], ['label2', 'label3'], ['admin2'])
593 self.mox.ReplayAll()
594 self.features_service.UpdateFilterRules(
595 self.cnxn, 12345, [rule1, rule2])
596 self.mox.VerifyAll()
597
598 def testExpungeFilterRules(self):
599 self.features_service.filterrule_tbl.Delete(self.cnxn, project_id=12345)
600 self.mox.ReplayAll()
601 self.features_service.ExpungeFilterRules(
602 self.cnxn, 12345)
603 self.mox.VerifyAll()
604
605 def testExpungeFilterRulesByUser(self):
606 emails = {'chicken@farm.test': 333, 'cow@fart.test': 222}
607 project_1_keep_rows = [
608 (1, 1, 'label:no-match-here', 'add_label:should-be-deleted-inserted')]
609 project_16_keep_rows =[
610 (16, 20, 'label:no-match-here', 'add_label:should-be-deleted-inserted'),
611 (16, 21, 'owner:rainbow@test.com', 'add_label:delete-and-insert')]
612 random_row = [
613 (19, 9, 'label:no-match-in-project', 'add_label:no-DELETE-INSERTROW')]
614 rows_to_delete = [
615 (1, 45, 'owner:cow@fart.test', 'add_label:happy-cows'),
616 (1, 46, 'owner:cow@fart.test', 'add_label:balloon'),
617 (16, 47, 'label:queue-eggs', 'add_notify:chicken@farm.test'),
618 (17, 48, 'owner:farmer@farm.test', 'add_cc_id:111 add_cc_id:222'),
619 (17, 48, 'label:queue-chickens', 'default_owner_id:333'),
620 ]
621 rows = (rows_to_delete + project_1_keep_rows + project_16_keep_rows +
622 random_row)
623 self.features_service.filterrule_tbl.Select = mock.Mock(return_value=rows)
624 self.features_service.filterrule_tbl.Delete = mock.Mock()
625
626 rules_dict = self.features_service.ExpungeFilterRulesByUser(
627 self.cnxn, emails)
628 expected_dict = {
629 1: [tracker_pb2.FilterRule(
630 predicate=rows[0][2], add_labels=['happy-cows']),
631 tracker_pb2.FilterRule(
632 predicate=rows[1][2], add_labels=['balloon'])],
633 16: [tracker_pb2.FilterRule(
634 predicate=rows[2][2], add_notify_addrs=['chicken@farm.test'])],
635 17: [tracker_pb2.FilterRule(
636 predicate=rows[3][2], add_cc_ids=[111, 222])],
637 }
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100638 six.assertCountEqual(self, rules_dict, expected_dict)
Copybara854996b2021-09-07 19:36:02 +0000639
640 self.features_service.filterrule_tbl.Select.assert_called_once_with(
641 self.cnxn, features_svc.FILTERRULE_COLS)
642
643 calls = [mock.call(self.cnxn, project_id=project_id, rank=rank,
644 predicate=predicate, consequence=consequence,
645 commit=False)
646 for (project_id, rank, predicate, consequence) in rows_to_delete]
647 self.features_service.filterrule_tbl.Delete.assert_has_calls(
648 calls, any_order=True)
649
650 def testExpungeFilterRulesByUser_EmptyUsers(self):
651 self.features_service.filterrule_tbl.Select = mock.Mock()
652 self.features_service.filterrule_tbl.Delete = mock.Mock()
653
654 rules_dict = self.features_service.ExpungeFilterRulesByUser(self.cnxn, {})
655 self.assertEqual(rules_dict, {})
656 self.features_service.filterrule_tbl.Select.assert_not_called()
657 self.features_service.filterrule_tbl.Delete.assert_not_called()
658
659 def testExpungeFilterRulesByUser_NoMatch(self):
660 rows = [
661 (17, 48, 'owner:farmer@farm.test', 'add_cc_id:111 add_cc_id: 222'),
662 (19, 9, 'label:no-match-in-project', 'add_label:no-DELETE-INSERTROW'),
663 ]
664 self.features_service.filterrule_tbl.Select = mock.Mock(return_value=rows)
665 self.features_service.filterrule_tbl.Delete = mock.Mock()
666
667 emails = {'cow@fart.test': 222}
668 rules_dict = self.features_service.ExpungeFilterRulesByUser(
669 self.cnxn, emails)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100670 six.assertCountEqual(self, rules_dict, {})
Copybara854996b2021-09-07 19:36:02 +0000671
672 self.features_service.filterrule_tbl.Select.assert_called_once_with(
673 self.cnxn, features_svc.FILTERRULE_COLS)
674 self.features_service.filterrule_tbl.Delete.assert_not_called()
675
676 ### Hotlists
677
678 def SetUpCreateHotlist(self):
679 # Check for the existing hotlist: there should be none.
680 # Two hotlists named 'hot1' exist but neither are owned by the user.
681 self.features_service.hotlist2user_tbl.Select(
682 self.cnxn, cols=['hotlist_id', 'user_id'],
683 user_id=[567], role_name='owner').AndReturn([])
684
685 self.features_service.hotlist_tbl.Select(
686 self.cnxn, cols=['id', 'name'], id=[], is_deleted=False,
687 where =[(('LOWER(name) IN (%s)'), ['hot1'])]).AndReturn([])
688
689 # Inserting the hotlist returns the id.
690 self.features_service.hotlist_tbl.InsertRow(
691 self.cnxn, name='hot1', summary='hot 1', description='test hotlist',
692 is_private=False,
693 default_col_spec=features_constants.DEFAULT_COL_SPEC).AndReturn(123)
694
695 # Insert the issues: there are none.
696 self.features_service.hotlist2issue_tbl.InsertRows(
697 self.cnxn, features_svc.HOTLIST2ISSUE_COLS,
698 [], commit=False)
699
700 # Insert the users: there is one owner and one editor.
701 self.features_service.hotlist2user_tbl.InsertRows(
702 self.cnxn, ['hotlist_id', 'user_id', 'role_name'],
703 [(123, 567, 'owner'), (123, 678, 'editor')])
704
705 def testCreateHotlist(self):
706 self.SetUpCreateHotlist()
707 self.mox.ReplayAll()
708 self.features_service.CreateHotlist(
709 self.cnxn, 'hot1', 'hot 1', 'test hotlist', [567], [678])
710 self.mox.VerifyAll()
711
712 def testCreateHotlist_InvalidName(self):
713 with self.assertRaises(exceptions.InputException):
714 self.features_service.CreateHotlist(
715 self.cnxn, '***Invalid name***', 'Misnamed Hotlist',
716 'A Hotlist with an invalid name', [567], [678])
717
718 def testCreateHotlist_NoOwner(self):
719 with self.assertRaises(features_svc.UnownedHotlistException):
720 self.features_service.CreateHotlist(
721 self.cnxn, 'unowned-hotlist', 'Unowned Hotlist',
722 'A Hotlist that is not owned', [], [])
723
724 def testCreateHotlist_HotlistAlreadyExists(self):
725 self.features_service.hotlist_id_2lc.CacheItem(('fake-hotlist', 567), 123)
726 with self.assertRaises(features_svc.HotlistAlreadyExists):
727 self.features_service.CreateHotlist(
728 self.cnxn, 'Fake-Hotlist', 'Misnamed Hotlist',
729 'This name is already in use', [567], [678])
730
731 def testTransferHotlistOwnership(self):
732 hotlist_id = 123
733 new_owner_id = 222
734 hotlist = fake.Hotlist(hotlist_name='unique', hotlist_id=hotlist_id,
735 owner_ids=[111], editor_ids=[222, 333],
736 follower_ids=[444])
737 # LookupHotlistIDs, proposed new owner, owns no hotlist with the same name.
738 self.features_service.hotlist2user_tbl.Select = mock.Mock(
739 return_value=[(223, new_owner_id), (567, new_owner_id)])
740 self.features_service.hotlist_tbl.Select = mock.Mock(return_value=[])
741
742 # UpdateHotlistRoles
743 self.features_service.GetHotlist = mock.Mock(return_value=hotlist)
744 self.features_service.hotlist2user_tbl.Delete = mock.Mock()
745 self.features_service.hotlist2user_tbl.InsertRows = mock.Mock()
746
747 self.features_service.TransferHotlistOwnership(
748 self.cnxn, hotlist, new_owner_id, True)
749
750 self.features_service.hotlist2user_tbl.Delete.assert_called_once_with(
751 self.cnxn, hotlist_id=hotlist_id, commit=False)
752
753 self.features_service.GetHotlist.assert_called_once_with(
754 self.cnxn, hotlist_id, use_cache=False)
755 insert_rows = [(hotlist_id, new_owner_id, 'owner'),
756 (hotlist_id, 333, 'editor'),
757 (hotlist_id, 111, 'editor'),
758 (hotlist_id, 444, 'follower')]
759 self.features_service.hotlist2user_tbl.InsertRows.assert_called_once_with(
760 self.cnxn, features_svc.HOTLIST2USER_COLS, insert_rows, commit=False)
761
762 def testLookupHotlistIDs(self):
763 # Set up DB query mocks.
764 self.features_service.hotlist2user_tbl.Select = mock.Mock(return_value=[
765 (123, 222), (125, 333)])
766 self.features_service.hotlist_tbl.Select = mock.Mock(
767 return_value=[(123, 'q3-TODO'), (125, 'q4-TODO')])
768
769 self.features_service.hotlist_id_2lc.CacheItem(
770 ('q4-todo', 333), 124)
771
772 ret = self.features_service.LookupHotlistIDs(
773 self.cnxn, ['q3-todo', 'Q4-TODO'], [222, 333, 444])
774 self.assertEqual(ret, {('q3-todo', 222) : 123, ('q4-todo', 333): 124})
775 self.features_service.hotlist2user_tbl.Select.assert_called_once_with(
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100776 self.cnxn, cols=['hotlist_id', 'user_id'], user_id=[222, 333, 444],
Copybara854996b2021-09-07 19:36:02 +0000777 role_name='owner')
778 self.features_service.hotlist_tbl.Select.assert_called_once_with(
779 self.cnxn, cols=['id', 'name'], id=[123, 125], is_deleted=False,
780 where=[
781 (('LOWER(name) IN (%s,%s)'), ['q3-todo', 'q4-todo'])])
782
783 def SetUpLookupUserHotlists(self):
784 self.features_service.hotlist2user_tbl.Select(
785 self.cnxn, cols=['user_id', 'hotlist_id'],
786 user_id=[111], left_joins=[('Hotlist ON hotlist_id = id', [])],
787 where=[('Hotlist.is_deleted = %s', [False])]).AndReturn([(111, 123)])
788
789 def testLookupUserHotlists(self):
790 self.SetUpLookupUserHotlists()
791 self.mox.ReplayAll()
792 ret = self.features_service.LookupUserHotlists(
793 self.cnxn, [111])
794 self.assertEqual(ret, {111: [123]})
795 self.mox.VerifyAll()
796
797 def SetUpLookupIssueHotlists(self):
798 self.features_service.hotlist2issue_tbl.Select(
799 self.cnxn, cols=['hotlist_id', 'issue_id'],
800 issue_id=[987], left_joins=[('Hotlist ON hotlist_id = id', [])],
801 where=[('Hotlist.is_deleted = %s', [False])]).AndReturn([(123, 987)])
802
803 def testLookupIssueHotlists(self):
804 self.SetUpLookupIssueHotlists()
805 self.mox.ReplayAll()
806 ret = self.features_service.LookupIssueHotlists(
807 self.cnxn, [987])
808 self.assertEqual(ret, {987: [123]})
809 self.mox.VerifyAll()
810
811 def SetUpGetHotlists(
812 self, hotlist_id, hotlist_rows=None, issue_rows=None, role_rows=None):
813 if not hotlist_rows:
814 hotlist_rows = [(hotlist_id, 'hotlist2', 'test hotlist 2',
815 'test hotlist', False, '')]
816 if not issue_rows:
817 issue_rows=[]
818 if not role_rows:
819 role_rows=[]
820 self.features_service.hotlist_tbl.Select(
821 self.cnxn, cols=features_svc.HOTLIST_COLS,
822 id=[hotlist_id], is_deleted=False).AndReturn(hotlist_rows)
823 self.features_service.hotlist2user_tbl.Select(
824 self.cnxn, cols=['hotlist_id', 'user_id', 'role_name'],
825 hotlist_id=[hotlist_id]).AndReturn(role_rows)
826 self.features_service.hotlist2issue_tbl.Select(
827 self.cnxn, cols=features_svc.HOTLIST2ISSUE_COLS,
828 hotlist_id=[hotlist_id],
829 order_by=[('rank DESC', []), ('issue_id', [])]).AndReturn(issue_rows)
830
831 def SetUpUpdateHotlist(self, hotlist_id):
832 hotlist_rows = [
833 (hotlist_id, 'hotlist2', 'test hotlist 2', 'test hotlist', False, '')
834 ]
835 role_rows = [(hotlist_id, 111, 'owner')]
836
837 self.features_service.hotlist_tbl.Select = mock.Mock(
838 return_value=hotlist_rows)
839 self.features_service.hotlist2user_tbl.Select = mock.Mock(
840 return_value=role_rows)
841 self.features_service.hotlist2issue_tbl.Select = mock.Mock(return_value=[])
842
843 self.features_service.hotlist_tbl.Update = mock.Mock()
844 self.features_service.hotlist2user_tbl.Delete = mock.Mock()
845 self.features_service.hotlist2user_tbl.InsertRows = mock.Mock()
846
847 def testUpdateHotlist(self):
848 hotlist_id = 456
849 self.SetUpUpdateHotlist(hotlist_id)
850
851 self.features_service.UpdateHotlist(
852 self.cnxn,
853 hotlist_id,
854 summary='A better one-line summary',
855 owner_id=333,
856 add_editor_ids=[444, 555])
857 delta = {'summary': 'A better one-line summary'}
858 self.features_service.hotlist_tbl.Update.assert_called_once_with(
859 self.cnxn, delta, id=hotlist_id, commit=False)
860 self.features_service.hotlist2user_tbl.Delete.assert_called_once_with(
861 self.cnxn, hotlist_id=hotlist_id, role='owner', commit=False)
862 add_role_rows = [
863 (hotlist_id, 333, 'owner'), (hotlist_id, 444, 'editor'),
864 (hotlist_id, 555, 'editor')
865 ]
866 self.features_service.hotlist2user_tbl.InsertRows.assert_called_once_with(
867 self.cnxn, features_svc.HOTLIST2USER_COLS, add_role_rows, commit=False)
868
869 def testUpdateHotlist_NoRoleChanges(self):
870 hotlist_id = 456
871 self.SetUpUpdateHotlist(hotlist_id)
872
873 self.features_service.UpdateHotlist(self.cnxn, hotlist_id, name='chicken')
874 delta = {'name': 'chicken'}
875 self.features_service.hotlist_tbl.Update.assert_called_once_with(
876 self.cnxn, delta, id=hotlist_id, commit=False)
877 self.features_service.hotlist2user_tbl.Delete.assert_not_called()
878 self.features_service.hotlist2user_tbl.InsertRows.assert_not_called()
879
880 def testUpdateHotlist_NoOwnerChange(self):
881 hotlist_id = 456
882 self.SetUpUpdateHotlist(hotlist_id)
883
884 self.features_service.UpdateHotlist(
885 self.cnxn, hotlist_id, name='chicken', add_editor_ids=[
886 333,
887 ])
888 delta = {'name': 'chicken'}
889 self.features_service.hotlist_tbl.Update.assert_called_once_with(
890 self.cnxn, delta, id=hotlist_id, commit=False)
891 self.features_service.hotlist2user_tbl.Delete.assert_not_called()
892 self.features_service.hotlist2user_tbl.InsertRows.assert_called_once_with(
893 self.cnxn,
894 features_svc.HOTLIST2USER_COLS, [
895 (hotlist_id, 333, 'editor'),
896 ],
897 commit=False)
898
899 def SetUpRemoveHotlistEditors(self):
900 hotlist = fake.Hotlist(
901 hotlist_name='hotlist',
902 hotlist_id=456,
903 owner_ids=[111],
904 editor_ids=[222, 333, 444])
905 self.features_service.GetHotlist = mock.Mock(return_value=hotlist)
906 self.features_service.hotlist2user_tbl.Delete = mock.Mock()
907 return hotlist
908
909 def testRemoveHotlistEditors(self):
910 """We can remove editors from a hotlist."""
911 hotlist = self.SetUpRemoveHotlistEditors()
912 remove_editor_ids = [222, 333]
913 self.features_service.RemoveHotlistEditors(
914 self.cnxn, hotlist.hotlist_id, remove_editor_ids=remove_editor_ids)
915 self.features_service.hotlist2user_tbl.Delete.assert_called_once_with(
916 self.cnxn, hotlist_id=hotlist.hotlist_id, user_id=remove_editor_ids)
917 self.assertEqual(hotlist.editor_ids, [444])
918
919 def testRemoveHotlistEditors_NoOp(self):
920 """A NoOp update does not trigger and sql table calls."""
921 hotlist = self.SetUpRemoveHotlistEditors()
922 with self.assertRaises(exceptions.InputException):
923 self.features_service.RemoveHotlistEditors(
924 self.cnxn, hotlist.hotlist_id, remove_editor_ids=[])
925
926 def SetUpUpdateHotlistItemsFields(self, hotlist_id, issue_ids):
927 hotlist_rows = [(hotlist_id, 'hotlist', '', '', True, '')]
928 insert_rows = [(345, 11, 112, 333, 2002, ''),
929 (345, 33, 332, 333, 2002, ''),
930 (345, 55, 552, 333, 2002, '')]
931 issue_rows = [(345, 11, 1, 333, 2002, ''), (345, 33, 3, 333, 2002, ''),
932 (345, 55, 3, 333, 2002, '')]
933 self.SetUpGetHotlists(
934 hotlist_id, hotlist_rows=hotlist_rows, issue_rows=issue_rows)
935 self.features_service.hotlist2issue_tbl.Delete(
936 self.cnxn, hotlist_id=hotlist_id,
937 issue_id=issue_ids, commit=False)
938 self.features_service.hotlist2issue_tbl.InsertRows(
939 self.cnxn, cols=features_svc.HOTLIST2ISSUE_COLS,
940 row_values=insert_rows, commit=True)
941
942 def testUpdateHotlistItemsFields_Ranks(self):
943 hotlist_item_fields = [
944 (11, 1, 333, 2002, ''), (33, 3, 333, 2002, ''),
945 (55, 3, 333, 2002, '')]
946 hotlist = fake.Hotlist(hotlist_name='hotlist', hotlist_id=345,
947 hotlist_item_fields=hotlist_item_fields)
948 self.features_service.hotlist_2lc.CacheItem(345, hotlist)
949 relations_to_change = {11: 112, 33: 332, 55: 552}
950 issue_ids = [11, 33, 55]
951 self.SetUpUpdateHotlistItemsFields(345, issue_ids)
952 self.mox.ReplayAll()
953 self.features_service.UpdateHotlistItemsFields(
954 self.cnxn, 345, new_ranks=relations_to_change)
955 self.mox.VerifyAll()
956
957 def testUpdateHotlistItemsFields_Notes(self):
958 pass
959
960 def testGetHotlists(self):
961 hotlist1 = fake.Hotlist(hotlist_name='hotlist1', hotlist_id=123)
962 self.features_service.hotlist_2lc.CacheItem(123, hotlist1)
963 self.SetUpGetHotlists(456)
964 self.mox.ReplayAll()
965 hotlist_dict = self.features_service.GetHotlists(
966 self.cnxn, [123, 456])
967 self.mox.VerifyAll()
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100968 six.assertCountEqual(self, [123, 456], list(hotlist_dict.keys()))
Copybara854996b2021-09-07 19:36:02 +0000969 self.assertEqual('hotlist1', hotlist_dict[123].name)
970 self.assertEqual('hotlist2', hotlist_dict[456].name)
971
972 def testGetHotlistsByID(self):
973 hotlist1 = fake.Hotlist(hotlist_name='hotlist1', hotlist_id=123)
974 self.features_service.hotlist_2lc.CacheItem(123, hotlist1)
975 # NOTE: The setup function must take a hotlist_id that is different
976 # from what was used in previous tests, otherwise the methods in the
977 # setup function will never get called.
978 self.SetUpGetHotlists(456)
979 self.mox.ReplayAll()
980 _, actual_missed = self.features_service.GetHotlistsByID(
981 self.cnxn, [123, 456])
982 self.mox.VerifyAll()
983 self.assertEqual(actual_missed, [])
984
985 def testGetHotlistsByUserID(self):
986 self.SetUpLookupUserHotlists()
987 self.SetUpGetHotlists(123)
988 self.mox.ReplayAll()
989 hotlists = self.features_service.GetHotlistsByUserID(self.cnxn, 111)
990 self.assertEqual(len(hotlists), 1)
991 self.assertEqual(hotlists[0].hotlist_id, 123)
992 self.mox.VerifyAll()
993
994 def testGetHotlistsByIssueID(self):
995 self.SetUpLookupIssueHotlists()
996 self.SetUpGetHotlists(123)
997 self.mox.ReplayAll()
998 hotlists = self.features_service.GetHotlistsByIssueID(self.cnxn, 987)
999 self.assertEqual(len(hotlists), 1)
1000 self.assertEqual(hotlists[0].hotlist_id, 123)
1001 self.mox.VerifyAll()
1002
1003 def SetUpUpdateHotlistRoles(
1004 self, hotlist_id, owner_ids, editor_ids, follower_ids):
1005
1006 self.features_service.hotlist2user_tbl.Delete(
1007 self.cnxn, hotlist_id=hotlist_id, commit=False)
1008
1009 insert_rows = [(hotlist_id, user_id, 'owner') for user_id in owner_ids]
1010 insert_rows.extend(
1011 [(hotlist_id, user_id, 'editor') for user_id in editor_ids])
1012 insert_rows.extend(
1013 [(hotlist_id, user_id, 'follower') for user_id in follower_ids])
1014 self.features_service.hotlist2user_tbl.InsertRows(
1015 self.cnxn, ['hotlist_id', 'user_id', 'role_name'],
1016 insert_rows, commit=False)
1017
1018 self.cnxn.Commit()
1019
1020 def testUpdateHotlistRoles(self):
1021 self.SetUpGetHotlists(456)
1022 self.SetUpUpdateHotlistRoles(456, [111, 222], [333], [])
1023 self.mox.ReplayAll()
1024 self.features_service.UpdateHotlistRoles(
1025 self.cnxn, 456, [111, 222], [333], [])
1026 self.mox.VerifyAll()
1027
1028 def SetUpUpdateHotlistIssues(self, items):
1029 hotlist = fake.Hotlist(hotlist_name='hotlist', hotlist_id=456)
1030 hotlist.items = items
1031 self.features_service.GetHotlist = mock.Mock(return_value=hotlist)
1032 self.features_service.hotlist2issue_tbl.Delete = mock.Mock()
1033 self.features_service.hotlist2issue_tbl.InsertRows = mock.Mock()
1034 self.issue_service.GetIssues = mock.Mock()
1035 return hotlist
1036
1037 def testUpdateHotlistIssues_ChangeIssues(self):
1038 original_items = [
1039 features_pb2.Hotlist.HotlistItem(
1040 issue_id=78902, rank=11, adder_id=333, date_added=2345), # update
1041 features_pb2.Hotlist.HotlistItem(
1042 issue_id=78904, rank=0, adder_id=333, date_added=2345) # same
1043 ]
1044 hotlist = self.SetUpUpdateHotlistIssues(original_items)
1045 updated_items = [
1046 features_pb2.Hotlist.HotlistItem(
1047 issue_id=78902, rank=13, adder_id=333, date_added=2345), # update
1048 features_pb2.Hotlist.HotlistItem(
1049 issue_id=78903, rank=23, adder_id=333, date_added=2345) # new
1050 ]
1051
1052 self.features_service.UpdateHotlistIssues(
1053 self.cnxn, hotlist.hotlist_id, updated_items, [], self.issue_service,
1054 self.chart_service)
1055
1056 insert_rows = [
1057 (hotlist.hotlist_id, 78902, 13, 333, 2345, ''),
1058 (hotlist.hotlist_id, 78903, 23, 333, 2345, '')
1059 ]
1060 self.features_service.hotlist2issue_tbl.InsertRows.assert_called_once_with(
1061 self.cnxn,
1062 cols=features_svc.HOTLIST2ISSUE_COLS,
1063 row_values=insert_rows,
1064 commit=False)
1065 self.features_service.hotlist2issue_tbl.Delete.assert_called_once_with(
1066 self.cnxn,
1067 hotlist_id=hotlist.hotlist_id,
1068 issue_id=[78902, 78903],
1069 commit=False)
1070
1071 # New hotlist itmes includes updated_items and unchanged items.
1072 expected_all_items = [
1073 features_pb2.Hotlist.HotlistItem(
1074 issue_id=78904, rank=0, adder_id=333, date_added=2345),
1075 features_pb2.Hotlist.HotlistItem(
1076 issue_id=78902, rank=13, adder_id=333, date_added=2345),
1077 features_pb2.Hotlist.HotlistItem(
1078 issue_id=78903, rank=23, adder_id=333, date_added=2345)
1079 ]
1080 self.assertEqual(hotlist.items, expected_all_items)
1081
1082 # Assert we're storing the new snapshots of the affected issues.
1083 self.issue_service.GetIssues.assert_called_once_with(
1084 self.cnxn, [78902, 78903])
1085
1086 def testUpdateHotlistIssues_RemoveIssues(self):
1087 original_items = [
1088 features_pb2.Hotlist.HotlistItem(
1089 issue_id=78901, rank=10, adder_id=222, date_added=2348), # remove
1090 features_pb2.Hotlist.HotlistItem(
1091 issue_id=78904, rank=0, adder_id=333, date_added=2345), # same
1092 ]
1093 hotlist = self.SetUpUpdateHotlistIssues(original_items)
1094 remove_issue_ids = [78901]
1095
1096 self.features_service.UpdateHotlistIssues(
1097 self.cnxn, hotlist.hotlist_id, [], remove_issue_ids, self.issue_service,
1098 self.chart_service)
1099
1100 self.features_service.hotlist2issue_tbl.Delete.assert_called_once_with(
1101 self.cnxn,
1102 hotlist_id=hotlist.hotlist_id,
1103 issue_id=remove_issue_ids,
1104 commit=False)
1105
1106 # New hotlist itmes includes updated_items and unchanged items.
1107 expected_all_items = [
1108 features_pb2.Hotlist.HotlistItem(
1109 issue_id=78904, rank=0, adder_id=333, date_added=2345)
1110 ]
1111 self.assertEqual(hotlist.items, expected_all_items)
1112
1113 # Assert we're storing the new snapshots of the affected issues.
1114 self.issue_service.GetIssues.assert_called_once_with(self.cnxn, [78901])
1115
1116 def testUpdateHotlistIssues_RemoveAndChange(self):
1117 original_items = [
1118 features_pb2.Hotlist.HotlistItem(
1119 issue_id=78901, rank=10, adder_id=222, date_added=2348), # remove
1120 features_pb2.Hotlist.HotlistItem(
1121 issue_id=78902, rank=11, adder_id=333, date_added=2345), # update
1122 features_pb2.Hotlist.HotlistItem(
1123 issue_id=78904, rank=0, adder_id=333, date_added=2345) # same
1124 ]
1125 hotlist = self.SetUpUpdateHotlistIssues(original_items)
1126 # test 78902 gets added back with `updated_items`
1127 remove_issue_ids = [78901, 78902]
1128 updated_items = [
1129 features_pb2.Hotlist.HotlistItem(
1130 issue_id=78902, rank=13, adder_id=333, date_added=2345),
1131 ]
1132
1133 self.features_service.UpdateHotlistIssues(
1134 self.cnxn, hotlist.hotlist_id, updated_items, remove_issue_ids,
1135 self.issue_service, self.chart_service)
1136
1137 delete_calls = [
1138 mock.call(
1139 self.cnxn,
1140 hotlist_id=hotlist.hotlist_id,
1141 issue_id=remove_issue_ids,
1142 commit=False),
1143 mock.call(
1144 self.cnxn,
1145 hotlist_id=hotlist.hotlist_id,
1146 issue_id=[78902],
1147 commit=False)
1148 ]
1149 self.assertEqual(
1150 self.features_service.hotlist2issue_tbl.Delete.mock_calls, delete_calls)
1151
1152 insert_rows = [
1153 (hotlist.hotlist_id, 78902, 13, 333, 2345, ''),
1154 ]
1155 self.features_service.hotlist2issue_tbl.InsertRows.assert_called_once_with(
1156 self.cnxn,
1157 cols=features_svc.HOTLIST2ISSUE_COLS,
1158 row_values=insert_rows,
1159 commit=False)
1160
1161 # New hotlist itmes includes updated_items and unchanged items.
1162 expected_all_items = [
1163 features_pb2.Hotlist.HotlistItem(
1164 issue_id=78904, rank=0, adder_id=333, date_added=2345),
1165 features_pb2.Hotlist.HotlistItem(
1166 issue_id=78902, rank=13, adder_id=333, date_added=2345),
1167 ]
1168 self.assertEqual(hotlist.items, expected_all_items)
1169
1170 # Assert we're storing the new snapshots of the affected issues.
1171 self.issue_service.GetIssues.assert_called_once_with(
1172 self.cnxn, [78901, 78902])
1173
1174 def testUpdateHotlistIssues_NoChanges(self):
1175 with self.assertRaises(exceptions.InputException):
1176 self.features_service.UpdateHotlistIssues(
1177 self.cnxn, 456, [], None, self.issue_service, self.chart_service)
1178
1179 def SetUpUpdateHotlistItems(self, cnxn, hotlist_id, remove, added_tuples):
1180 self.features_service.hotlist2issue_tbl.Delete(
1181 cnxn, hotlist_id=hotlist_id, issue_id=remove, commit=False)
1182 rank = 1
1183 added_tuples_with_rank = [(issue_id, rank+10*mult, user_id, ts, note) for
1184 mult, (issue_id, user_id, ts, note) in
1185 enumerate(added_tuples)]
1186 insert_rows = [(hotlist_id, issue_id,
1187 rank, user_id, date, note) for
1188 (issue_id, rank, user_id, date, note) in
1189 added_tuples_with_rank]
1190 self.features_service.hotlist2issue_tbl.InsertRows(
1191 cnxn, cols=features_svc.HOTLIST2ISSUE_COLS,
1192 row_values=insert_rows, commit=False)
1193
1194 def testAddIssuesToHotlists(self):
1195 added_tuples = [
1196 (111, None, None, ''),
1197 (222, None, None, ''),
1198 (333, None, None, '')]
1199 issues = [
1200 tracker_pb2.Issue(issue_id=issue_id)
1201 for issue_id, _, _, _ in added_tuples
1202 ]
1203 self.SetUpGetHotlists(456)
1204 self.SetUpUpdateHotlistItems(
1205 self.cnxn, 456, [], added_tuples)
1206 self.SetUpGetHotlists(567)
1207 self.SetUpUpdateHotlistItems(
1208 self.cnxn, 567, [], added_tuples)
1209
1210 self.mox.StubOutWithMock(self.issue_service, 'GetIssues')
1211 self.issue_service.GetIssues(self.cnxn,
1212 [111, 222, 333]).AndReturn(issues)
1213 self.chart_service.StoreIssueSnapshots(self.cnxn, issues,
1214 commit=False)
1215 self.mox.ReplayAll()
1216 self.features_service.AddIssuesToHotlists(
1217 self.cnxn, [456, 567], added_tuples, self.issue_service,
1218 self.chart_service, commit=False)
1219 self.mox.VerifyAll()
1220
1221 def testRemoveIssuesFromHotlists(self):
1222 issue_rows = [
1223 (456, 555, 1, None, None, ''),
1224 (456, 666, 11, None, None, ''),
1225 ]
1226 issues = [tracker_pb2.Issue(issue_id=issue_rows[0][1])]
1227 self.SetUpGetHotlists(456, issue_rows=issue_rows)
1228 self.SetUpUpdateHotlistItems(
1229 self. cnxn, 456, [555], [])
1230 issue_rows = [
1231 (789, 555, 1, None, None, ''),
1232 (789, 666, 11, None, None, ''),
1233 ]
1234 self.SetUpGetHotlists(789, issue_rows=issue_rows)
1235 self.SetUpUpdateHotlistItems(
1236 self. cnxn, 789, [555], [])
1237 self.mox.StubOutWithMock(self.issue_service, 'GetIssues')
1238 self.issue_service.GetIssues(self.cnxn,
1239 [555]).AndReturn(issues)
1240 self.chart_service.StoreIssueSnapshots(self.cnxn, issues, commit=False)
1241 self.mox.ReplayAll()
1242 self.features_service.RemoveIssuesFromHotlists(
1243 self.cnxn, [456, 789], [555], self.issue_service, self.chart_service,
1244 commit=False)
1245 self.mox.VerifyAll()
1246
1247 def testUpdateHotlistItems(self):
1248 self.SetUpGetHotlists(456)
1249 self.SetUpUpdateHotlistItems(
1250 self. cnxn, 456, [], [
1251 (111, None, None, ''),
1252 (222, None, None, ''),
1253 (333, None, None, '')])
1254 self.mox.ReplayAll()
1255 self.features_service.UpdateHotlistItems(
1256 self.cnxn, 456, [],
1257 [(111, None, None, ''),
1258 (222, None, None, ''),
1259 (333, None, None, '')], commit=False)
1260 self.mox.VerifyAll()
1261
1262 def SetUpDeleteHotlist(self, cnxn, hotlist_id):
1263 hotlist_rows = [(hotlist_id, 'hotlist', 'test hotlist',
1264 'test list', False, '')]
1265 self.SetUpGetHotlists(678, hotlist_rows=hotlist_rows,
1266 role_rows=[(hotlist_id, 111, 'owner', )])
1267 self.features_service.hotlist2issue_tbl.Select(self.cnxn,
1268 cols=['Issue.project_id'], hotlist_id=hotlist_id, distinct=True,
1269 left_joins=[('Issue ON issue_id = id', [])]).AndReturn([(1,)])
1270 self.features_service.hotlist_tbl.Update(cnxn, {'is_deleted': True},
1271 commit=False, id=hotlist_id)
1272
1273 def testDeleteHotlist(self):
1274 self.SetUpDeleteHotlist(self.cnxn, 678)
1275 self.mox.ReplayAll()
1276 self.features_service.DeleteHotlist(self.cnxn, 678, commit=False)
1277 self.mox.VerifyAll()
1278
1279 def testExpungeHotlists(self):
1280 hotliststar_tbl = mock.Mock()
1281 star_service = star_svc.AbstractStarService(
1282 self.cache_manager, hotliststar_tbl, 'hotlist_id', 'user_id', 'hotlist')
1283 hotliststar_tbl.Delete = mock.Mock()
1284 user_service = user_svc.UserService(self.cache_manager)
1285 user_service.hotlistvisithistory_tbl.Delete = mock.Mock()
1286 chart_service = chart_svc.ChartService(self.config_service)
1287 self.cnxn.Execute = mock.Mock()
1288
1289 hotlist1 = fake.Hotlist(hotlist_name='unique', hotlist_id=678,
1290 owner_ids=[111], editor_ids=[222, 333])
1291 hotlist2 = fake.Hotlist(hotlist_name='unique2', hotlist_id=679,
1292 owner_ids=[111])
1293 hotlists_by_id = {hotlist1.hotlist_id: hotlist1,
1294 hotlist2.hotlist_id: hotlist2}
1295 self.features_service.GetHotlists = mock.Mock(return_value=hotlists_by_id)
1296 self.features_service.hotlist2user_tbl.Delete = mock.Mock()
1297 self.features_service.hotlist2issue_tbl.Delete = mock.Mock()
1298 self.features_service.hotlist_tbl.Delete = mock.Mock()
1299 # cache invalidation mocks
1300 self.features_service.hotlist_2lc.InvalidateKeys = mock.Mock()
1301 self.features_service.hotlist_id_2lc.InvalidateKeys = mock.Mock()
1302 self.features_service.hotlist_user_to_ids.InvalidateKeys = mock.Mock()
1303 self.config_service.InvalidateMemcacheForEntireProject = mock.Mock()
1304
1305 hotlists_project_id = 787
1306 self.features_service.GetProjectIDsFromHotlist = mock.Mock(
1307 return_value=[hotlists_project_id])
1308
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001309 hotlist_ids = list(hotlists_by_id.keys())
Copybara854996b2021-09-07 19:36:02 +00001310 commit = True # commit in ExpungeHotlists should be True by default.
1311 self.features_service.ExpungeHotlists(
1312 self.cnxn, hotlist_ids, star_service, user_service, chart_service)
1313
1314 star_calls = [
1315 mock.call(
1316 self.cnxn, commit=commit, limit=None, hotlist_id=hotlist_ids[0]),
1317 mock.call(
1318 self.cnxn, commit=commit, limit=None, hotlist_id=hotlist_ids[1])]
1319 hotliststar_tbl.Delete.assert_has_calls(star_calls)
1320
1321 self.cnxn.Execute.assert_called_once_with(
1322 'DELETE FROM IssueSnapshot2Hotlist WHERE hotlist_id IN (%s,%s)',
1323 [678, 679], commit=commit)
1324 user_service.hotlistvisithistory_tbl.Delete.assert_called_once_with(
1325 self.cnxn, commit=commit, hotlist_id=hotlist_ids)
1326
1327 self.features_service.hotlist2user_tbl.Delete.assert_called_once_with(
1328 self.cnxn, hotlist_id=hotlist_ids, commit=commit)
1329 self.features_service.hotlist2issue_tbl.Delete.assert_called_once_with(
1330 self.cnxn, hotlist_id=hotlist_ids, commit=commit)
1331 self.features_service.hotlist_tbl.Delete.assert_called_once_with(
1332 self.cnxn, id=hotlist_ids, commit=commit)
1333 # cache invalidation checks
1334 self.features_service.hotlist_2lc.InvalidateKeys.assert_called_once_with(
1335 self.cnxn, hotlist_ids)
1336 invalidate_owner_calls = [
1337 mock.call(self.cnxn, [(hotlist1.name, hotlist1.owner_ids[0])]),
1338 mock.call(self.cnxn, [(hotlist2.name, hotlist2.owner_ids[0])])]
1339 self.features_service.hotlist_id_2lc.InvalidateKeys.assert_has_calls(
1340 invalidate_owner_calls)
1341 self.features_service.hotlist_user_to_ids.InvalidateKeys.\
1342assert_called_once_with(
1343 self.cnxn, [333, 222, 111])
1344 self.config_service.InvalidateMemcacheForEntireProject.\
1345assert_called_once_with(hotlists_project_id)
1346
1347 def testExpungeUsersInHotlists(self):
1348 hotliststar_tbl = mock.Mock()
1349 star_service = star_svc.AbstractStarService(
1350 self.cache_manager, hotliststar_tbl, 'hotlist_id', 'user_id', 'hotlist')
1351 user_service = user_svc.UserService(self.cache_manager)
1352 chart_service = chart_svc.ChartService(self.config_service)
1353 user_ids = [111, 222]
1354
1355 # hotlist1 will get transferred to 333
1356 hotlist1 = fake.Hotlist(hotlist_name='unique', hotlist_id=123,
1357 owner_ids=[111], editor_ids=[222, 333])
1358 # hotlist2 will get deleted
1359 hotlist2 = fake.Hotlist(hotlist_name='name', hotlist_id=223,
1360 owner_ids=[222], editor_ids=[111, 333])
1361 delete_hotlists = [hotlist2.hotlist_id]
1362 delete_hotlist_project_id = 788
1363 self.features_service.GetProjectIDsFromHotlist = mock.Mock(
1364 return_value=[delete_hotlist_project_id])
1365 self.config_service.InvalidateMemcacheForEntireProject = mock.Mock()
1366 hotlists_by_user_id = {
1367 111: [hotlist1.hotlist_id, hotlist2.hotlist_id],
1368 222: [hotlist1.hotlist_id, hotlist2.hotlist_id],
1369 333: [hotlist1.hotlist_id, hotlist2.hotlist_id]}
1370 self.features_service.LookupUserHotlists = mock.Mock(
1371 return_value=hotlists_by_user_id)
1372 hotlists_by_id = {hotlist1.hotlist_id: hotlist1,
1373 hotlist2.hotlist_id: hotlist2}
1374 self.features_service.GetHotlistsByID = mock.Mock(
1375 return_value=(hotlists_by_id, []))
1376
1377 # User 333 already has a hotlist named 'name'.
1378 def side_effect(_cnxn, hotlist_names, owner_ids):
1379 if 333 in owner_ids and 'name' in hotlist_names:
1380 return {('name', 333): 567}
1381 return {}
1382 self.features_service.LookupHotlistIDs = mock.Mock(
1383 side_effect=side_effect)
1384 # Called to transfer hotlist ownership
1385 self.features_service.UpdateHotlistRoles = mock.Mock()
1386
1387 # Called to expunge users and hotlists
1388 self.features_service.hotlist2user_tbl.Delete = mock.Mock()
1389 self.features_service.hotlist2issue_tbl.Update = mock.Mock()
1390 user_service.hotlistvisithistory_tbl.Delete = mock.Mock()
1391
1392 # Called to expunge hotlists
1393 hotlists_by_id = {hotlist1.hotlist_id: hotlist1,
1394 hotlist2.hotlist_id: hotlist2}
1395 self.features_service.GetHotlists = mock.Mock(
1396 return_value=hotlists_by_id)
1397 self.features_service.hotlist2issue_tbl.Delete = mock.Mock()
1398 self.features_service.hotlist_tbl.Delete = mock.Mock()
1399 hotliststar_tbl.Delete = mock.Mock()
1400
1401 self.features_service.ExpungeUsersInHotlists(
1402 self.cnxn, user_ids, star_service, user_service, chart_service)
1403
1404 self.features_service.UpdateHotlistRoles.assert_called_once_with(
1405 self.cnxn, hotlist1.hotlist_id, [333], [222], [], commit=False)
1406
1407 self.features_service.hotlist2user_tbl.Delete.assert_has_calls(
1408 [mock.call(self.cnxn, user_id=user_ids, commit=False),
1409 mock.call(self.cnxn, hotlist_id=delete_hotlists, commit=False)])
1410 self.features_service.hotlist2issue_tbl.Update.assert_called_once_with(
1411 self.cnxn, {'adder_id': framework_constants.DELETED_USER_ID},
1412 adder_id=user_ids, commit=False)
1413 user_service.hotlistvisithistory_tbl.Delete.assert_has_calls(
1414 [mock.call(self.cnxn, user_id=user_ids, commit=False),
1415 mock.call(self.cnxn, hotlist_id=delete_hotlists, commit=False)])
1416
1417 self.features_service.hotlist2issue_tbl.Delete.assert_called_once_with(
1418 self.cnxn, hotlist_id=delete_hotlists, commit=False)
1419 hotliststar_tbl.Delete.assert_called_once_with(
1420 self.cnxn, commit=False, limit=None, hotlist_id=delete_hotlists[0])
1421 self.features_service.hotlist_tbl.Delete.assert_called_once_with(
1422 self.cnxn, id=delete_hotlists, commit=False)
1423
1424
1425 def testGetProjectIDsFromHotlist(self):
1426 self.features_service.hotlist2issue_tbl.Select(self.cnxn,
1427 cols=['Issue.project_id'], hotlist_id=678, distinct=True,
1428 left_joins=[('Issue ON issue_id = id', [])]).AndReturn(
1429 [(789,), (787,), (788,)])
1430
1431 self.mox.ReplayAll()
1432 project_ids = self.features_service.GetProjectIDsFromHotlist(self.cnxn, 678)
1433 self.mox.VerifyAll()
1434 self.assertEqual([789, 787, 788], project_ids)