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