Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1 | # 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. |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 4 | |
| 5 | """Unittest for the dateaction module.""" |
| 6 | |
| 7 | from __future__ import division |
| 8 | from __future__ import print_function |
| 9 | from __future__ import absolute_import |
| 10 | |
| 11 | import logging |
| 12 | import mock |
| 13 | import time |
| 14 | import unittest |
| 15 | |
| 16 | from features import dateaction |
| 17 | from framework import cloud_tasks_helpers |
| 18 | from framework import framework_constants |
| 19 | from framework import framework_views |
| 20 | from framework import timestr |
| 21 | from framework import urls |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 22 | from mrproto import tracker_pb2 |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 23 | from services import service_manager |
| 24 | from testing import fake |
| 25 | from testing import testing_helpers |
| 26 | from tracker import tracker_bizobj |
| 27 | |
| 28 | |
| 29 | NOW = 1492120863 |
| 30 | |
| 31 | |
| 32 | class DateActionCronTest(unittest.TestCase): |
| 33 | |
| 34 | def setUp(self): |
| 35 | self.services = service_manager.Services( |
| 36 | user=fake.UserService(), |
| 37 | issue=fake.IssueService()) |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 38 | self.servlet = dateaction.DateActionCron(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 39 | self.TIMESTAMP_MIN = ( |
| 40 | NOW // framework_constants.SECS_PER_DAY * |
| 41 | framework_constants.SECS_PER_DAY) |
| 42 | self.TIMESTAMP_MAX = self.TIMESTAMP_MIN + framework_constants.SECS_PER_DAY |
| 43 | self.left_joins = [ |
| 44 | ('Issue2FieldValue ON Issue.id = Issue2FieldValue.issue_id', []), |
| 45 | ('FieldDef ON Issue2FieldValue.field_id = FieldDef.id', []), |
| 46 | ] |
| 47 | self.where = [ |
| 48 | ('FieldDef.field_type = %s', ['date_type']), |
| 49 | ( |
| 50 | 'FieldDef.date_action IN (%s,%s)', |
| 51 | ['ping_owner_only', 'ping_participants']), |
| 52 | ('Issue2FieldValue.date_value >= %s', [self.TIMESTAMP_MIN]), |
| 53 | ('Issue2FieldValue.date_value < %s', [self.TIMESTAMP_MAX]), |
| 54 | ] |
| 55 | self.order_by = [ |
| 56 | ('Issue.id', []), |
| 57 | ] |
| 58 | |
| 59 | @mock.patch('time.time', return_value=NOW) |
| 60 | def testHandleRequest_NoMatches(self, _mock_time): |
| 61 | _request, mr = testing_helpers.GetRequestObjects( |
| 62 | path=urls.DATE_ACTION_CRON) |
| 63 | self.services.issue.RunIssueQuery = mock.MagicMock(return_value=([], False)) |
| 64 | |
| 65 | self.servlet.HandleRequest(mr) |
| 66 | |
| 67 | self.services.issue.RunIssueQuery.assert_called_with( |
| 68 | mr.cnxn, self.left_joins, self.where + [('Issue.id > %s', [0])], |
| 69 | self.order_by) |
| 70 | |
| 71 | @mock.patch('framework.cloud_tasks_helpers._get_client') |
| 72 | @mock.patch('time.time', return_value=NOW) |
| 73 | def testHandleRequest_OneMatche(self, _mock_time, get_client_mock): |
| 74 | _request, mr = testing_helpers.GetRequestObjects( |
| 75 | path=urls.DATE_ACTION_CRON) |
| 76 | self.services.issue.RunIssueQuery = mock.MagicMock( |
| 77 | return_value=([78901], False)) |
| 78 | |
| 79 | self.servlet.HandleRequest(mr) |
| 80 | |
| 81 | self.services.issue.RunIssueQuery.assert_called_with( |
| 82 | mr.cnxn, self.left_joins, self.where + [('Issue.id > %s', [0])], |
| 83 | self.order_by) |
| 84 | expected_task = { |
| 85 | 'app_engine_http_request': |
| 86 | { |
| 87 | 'relative_uri': urls.ISSUE_DATE_ACTION_TASK + '.do', |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 88 | 'body': b'issue_id=78901', |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 89 | 'headers': { |
| 90 | 'Content-type': 'application/x-www-form-urlencoded' |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | get_client_mock().create_task.assert_any_call( |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 95 | parent=get_client_mock().queue_path(), |
| 96 | task=expected_task, |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 97 | retry=cloud_tasks_helpers._DEFAULT_RETRY) |
| 98 | |
| 99 | @mock.patch('framework.cloud_tasks_helpers._get_client') |
| 100 | def testEnqueueDateAction(self, get_client_mock): |
| 101 | self.servlet.EnqueueDateAction(78901) |
| 102 | expected_task = { |
| 103 | 'app_engine_http_request': |
| 104 | { |
| 105 | 'relative_uri': urls.ISSUE_DATE_ACTION_TASK + '.do', |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 106 | 'body': b'issue_id=78901', |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 107 | 'headers': { |
| 108 | 'Content-type': 'application/x-www-form-urlencoded' |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | get_client_mock().create_task.assert_any_call( |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 113 | parent=get_client_mock().queue_path(), |
| 114 | task=expected_task, |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 115 | retry=cloud_tasks_helpers._DEFAULT_RETRY) |
| 116 | |
| 117 | |
| 118 | class IssueDateActionTaskTest(unittest.TestCase): |
| 119 | |
| 120 | def setUp(self): |
| 121 | self.services = service_manager.Services( |
| 122 | user=fake.UserService(), |
| 123 | usergroup=fake.UserGroupService(), |
| 124 | features=fake.FeaturesService(), |
| 125 | issue=fake.IssueService(), |
| 126 | project=fake.ProjectService(), |
| 127 | config=fake.ConfigService(), |
| 128 | issue_star=fake.IssueStarService()) |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 129 | self.servlet = dateaction.IssueDateActionTask(services=self.services) |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 130 | |
| 131 | self.config = self.services.config.GetProjectConfig('cnxn', 789) |
| 132 | self.config.field_defs = [ |
| 133 | tracker_bizobj.MakeFieldDef( |
| 134 | 123, 789, 'NextAction', tracker_pb2.FieldTypes.DATE_TYPE, |
| 135 | '', '', False, False, False, None, None, None, False, '', |
| 136 | None, None, tracker_pb2.DateAction.PING_OWNER_ONLY, |
| 137 | 'Date of next expected progress update', False), |
| 138 | tracker_bizobj.MakeFieldDef( |
| 139 | 124, 789, 'EoL', tracker_pb2.FieldTypes.DATE_TYPE, |
| 140 | '', '', False, False, False, None, None, None, False, '', |
| 141 | None, None, tracker_pb2.DateAction.PING_OWNER_ONLY, 'doc', False), |
| 142 | tracker_bizobj.MakeFieldDef( |
| 143 | 125, 789, 'TLsBirthday', tracker_pb2.FieldTypes.DATE_TYPE, |
| 144 | '', '', False, False, False, None, None, None, False, '', |
| 145 | None, None, tracker_pb2.DateAction.NO_ACTION, 'doc', False), |
| 146 | ] |
| 147 | self.services.config.StoreConfig('cnxn', self.config) |
| 148 | self.project = self.services.project.TestAddProject('proj', project_id=789) |
| 149 | self.owner = self.services.user.TestAddUser('owner@example.com', 111) |
| 150 | self.date_action_user = self.services.user.TestAddUser( |
| 151 | 'date-action-user@example.com', 555) |
| 152 | |
| 153 | def testHandleRequest_IssueHasNoArrivedDates(self): |
| 154 | _request, mr = testing_helpers.GetRequestObjects( |
| 155 | path=urls.ISSUE_DATE_ACTION_TASK + '.do?issue_id=78901') |
| 156 | self.services.issue.TestAddIssue(fake.MakeTestIssue( |
| 157 | 789, 1, 'summary', 'New', 111, issue_id=78901)) |
| 158 | self.assertEqual(1, len(self.services.issue.GetCommentsForIssue( |
| 159 | mr.cnxn, 78901))) |
| 160 | |
| 161 | self.servlet.HandleRequest(mr) |
| 162 | self.assertEqual(1, len(self.services.issue.GetCommentsForIssue( |
| 163 | mr.cnxn, 78901))) |
| 164 | |
| 165 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 166 | def testHandleRequest_IssueHasOneArriveDate(self, create_task_mock): |
| 167 | _request, mr = testing_helpers.GetRequestObjects( |
| 168 | path=urls.ISSUE_DATE_ACTION_TASK + '.do?issue_id=78901') |
| 169 | |
| 170 | now = int(time.time()) |
| 171 | date_str = timestr.TimestampToDateWidgetStr(now) |
| 172 | issue = fake.MakeTestIssue(789, 1, 'summary', 'New', 111, issue_id=78901) |
| 173 | self.services.issue.TestAddIssue(issue) |
| 174 | issue.field_values = [ |
| 175 | tracker_bizobj.MakeFieldValue(123, None, None, None, now, None, False)] |
| 176 | self.assertEqual(1, len(self.services.issue.GetCommentsForIssue( |
| 177 | mr.cnxn, 78901))) |
| 178 | |
| 179 | self.servlet.HandleRequest(mr) |
| 180 | comments = self.services.issue.GetCommentsForIssue(mr.cnxn, 78901) |
| 181 | self.assertEqual(2, len(comments)) |
| 182 | self.assertEqual( |
| 183 | 'The NextAction date has arrived: %s' % date_str, |
| 184 | comments[1].content) |
| 185 | |
| 186 | self.assertEqual(create_task_mock.call_count, 1) |
| 187 | |
| 188 | (args, kwargs) = create_task_mock.call_args |
| 189 | self.assertEqual( |
| 190 | args[0]['app_engine_http_request']['relative_uri'], |
| 191 | urls.OUTBOUND_EMAIL_TASK + '.do') |
| 192 | self.assertEqual(kwargs['queue'], 'outboundemail') |
| 193 | |
| 194 | def SetUpFieldValues(self, issue, now): |
| 195 | issue.field_values = [ |
| 196 | tracker_bizobj.MakeFieldValue(123, None, None, None, now, None, False), |
| 197 | tracker_bizobj.MakeFieldValue(124, None, None, None, now, None, False), |
| 198 | tracker_bizobj.MakeFieldValue(125, None, None, None, now, None, False), |
| 199 | ] |
| 200 | |
| 201 | @mock.patch('framework.cloud_tasks_helpers.create_task') |
| 202 | def testHandleRequest_IssueHasTwoArriveDates(self, create_task_mock): |
| 203 | _request, mr = testing_helpers.GetRequestObjects( |
| 204 | path=urls.ISSUE_DATE_ACTION_TASK + '.do?issue_id=78901') |
| 205 | |
| 206 | now = int(time.time()) |
| 207 | date_str = timestr.TimestampToDateWidgetStr(now) |
| 208 | issue = fake.MakeTestIssue(789, 1, 'summary', 'New', 111, issue_id=78901) |
| 209 | self.services.issue.TestAddIssue(issue) |
| 210 | self.SetUpFieldValues(issue, now) |
| 211 | self.assertEqual(1, len(self.services.issue.GetCommentsForIssue( |
| 212 | mr.cnxn, 78901))) |
| 213 | |
| 214 | self.servlet.HandleRequest(mr) |
| 215 | comments = self.services.issue.GetCommentsForIssue(mr.cnxn, 78901) |
| 216 | self.assertEqual(2, len(comments)) |
| 217 | self.assertEqual( |
| 218 | 'The EoL date has arrived: %s\n' |
| 219 | 'The NextAction date has arrived: %s' % (date_str, date_str), |
| 220 | comments[1].content) |
| 221 | |
| 222 | self.assertEqual(create_task_mock.call_count, 1) |
| 223 | |
| 224 | (args, kwargs) = create_task_mock.call_args |
| 225 | self.assertEqual( |
| 226 | args[0]['app_engine_http_request']['relative_uri'], |
| 227 | urls.OUTBOUND_EMAIL_TASK + '.do') |
| 228 | self.assertEqual(kwargs['queue'], 'outboundemail') |
| 229 | |
| 230 | def MakePingComment(self): |
| 231 | comment = tracker_pb2.IssueComment() |
| 232 | comment.project_id = self.project.project_id |
| 233 | comment.user_id = self.date_action_user.user_id |
| 234 | comment.content = 'Some date(s) arrived...' |
| 235 | return comment |
| 236 | |
| 237 | def testMakeEmailTasks_Owner(self): |
| 238 | """The issue owner gets pinged and the email has expected content.""" |
| 239 | issue = fake.MakeTestIssue( |
| 240 | 789, 1, 'summary', 'New', self.owner.user_id, issue_id=78901) |
| 241 | self.services.issue.TestAddIssue(issue) |
| 242 | now = int(time.time()) |
| 243 | self.SetUpFieldValues(issue, now) |
| 244 | issue.project_name = 'proj' |
| 245 | comment = self.MakePingComment() |
| 246 | next_action_field_def = self.config.field_defs[0] |
| 247 | pings = [(next_action_field_def, now)] |
| 248 | users_by_id = framework_views.MakeAllUserViews( |
| 249 | 'fake cnxn', self.services.user, |
| 250 | [self.owner.user_id, self.date_action_user.user_id]) |
| 251 | |
| 252 | tasks = self.servlet._MakeEmailTasks( |
| 253 | 'fake cnxn', issue, self.project, self.config, comment, |
| 254 | [], 'example-app.appspot.com', users_by_id, pings) |
| 255 | self.assertEqual(1, len(tasks)) |
| 256 | notify_owner_task = tasks[0] |
| 257 | self.assertEqual('owner@example.com', notify_owner_task['to']) |
| 258 | self.assertEqual( |
| 259 | 'Follow up on issue 1 in proj: summary', |
| 260 | notify_owner_task['subject']) |
| 261 | body = notify_owner_task['body'] |
| 262 | self.assertIn(comment.content, body) |
| 263 | self.assertIn(next_action_field_def.docstring, body) |
| 264 | |
| 265 | def testMakeEmailTasks_Starrer(self): |
| 266 | """Users who starred the issue are notified iff they opt in.""" |
| 267 | issue = fake.MakeTestIssue( |
| 268 | 789, 1, 'summary', 'New', 0, issue_id=78901) |
| 269 | self.services.issue.TestAddIssue(issue) |
| 270 | now = int(time.time()) |
| 271 | self.SetUpFieldValues(issue, now) |
| 272 | issue.project_name = 'proj' |
| 273 | comment = self.MakePingComment() |
| 274 | next_action_field_def = self.config.field_defs[0] |
| 275 | pings = [(next_action_field_def, now)] |
| 276 | |
| 277 | starrer_333 = self.services.user.TestAddUser('starrer333@example.com', 333) |
| 278 | starrer_333.notify_starred_ping = True |
| 279 | self.services.user.TestAddUser('starrer444@example.com', 444) |
| 280 | starrer_ids = [333, 444] |
| 281 | users_by_id = framework_views.MakeAllUserViews( |
| 282 | 'fake cnxn', self.services.user, |
| 283 | [self.owner.user_id, self.date_action_user.user_id], |
| 284 | starrer_ids) |
| 285 | |
| 286 | tasks = self.servlet._MakeEmailTasks( |
| 287 | 'fake cnxn', issue, self.project, self.config, comment, |
| 288 | starrer_ids, 'example-app.appspot.com', users_by_id, pings) |
| 289 | self.assertEqual(1, len(tasks)) |
| 290 | notify_owner_task = tasks[0] |
| 291 | self.assertEqual('starrer333@example.com', notify_owner_task['to']) |