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