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 issue tracker views.""" |
| 6 | from __future__ import print_function |
| 7 | from __future__ import division |
| 8 | from __future__ import absolute_import |
| 9 | |
| 10 | import logging |
| 11 | import unittest |
| 12 | |
Adrià Vilanova Martínez | 9f9ade5 | 2022-10-10 23:20:11 +0200 | [diff] [blame] | 13 | try: |
| 14 | from mox3 import mox |
| 15 | except ImportError: |
| 16 | import mox |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 17 | |
| 18 | from google.appengine.api import app_identity |
| 19 | import ezt |
| 20 | |
| 21 | from framework import framework_views |
| 22 | from framework import gcs_helpers |
| 23 | from framework import template_helpers |
| 24 | from framework import urls |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 25 | from mrproto import project_pb2 |
| 26 | from mrproto import tracker_pb2 |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 27 | from services import service_manager |
| 28 | from testing import fake |
| 29 | from testing import testing_helpers |
| 30 | from tracker import attachment_helpers |
| 31 | from tracker import tracker_bizobj |
| 32 | from tracker import tracker_constants |
| 33 | from tracker import tracker_helpers |
| 34 | from tracker import tracker_views |
| 35 | |
| 36 | |
| 37 | def _Issue(project_name, local_id, summary, status): |
| 38 | issue = tracker_pb2.Issue() |
| 39 | issue.project_name = project_name |
| 40 | issue.local_id = local_id |
| 41 | issue.issue_id = 100000 + local_id |
| 42 | issue.summary = summary |
| 43 | issue.status = status |
| 44 | return issue |
| 45 | |
| 46 | |
| 47 | def _MakeConfig(): |
| 48 | config = tracker_pb2.ProjectIssueConfig() |
| 49 | config.well_known_labels = [ |
| 50 | tracker_pb2.LabelDef( |
| 51 | label='Priority-High', label_docstring='Must be resolved'), |
| 52 | tracker_pb2.LabelDef( |
| 53 | label='Priority-Low', label_docstring='Can be slipped'), |
| 54 | ] |
| 55 | config.well_known_statuses.append(tracker_pb2.StatusDef( |
| 56 | status='New', means_open=True)) |
| 57 | config.well_known_statuses.append(tracker_pb2.StatusDef( |
| 58 | status='Old', means_open=False)) |
| 59 | return config |
| 60 | |
| 61 | |
| 62 | class IssueViewTest(unittest.TestCase): |
| 63 | |
| 64 | def setUp(self): |
| 65 | self.issue1 = _Issue('proj', 1, 'not too long summary', 'New') |
| 66 | self.issue2 = _Issue('proj', 2, 'sum 2', '') |
| 67 | self.issue3 = _Issue('proj', 3, 'sum 3', '') |
| 68 | self.issue4 = _Issue('proj', 4, 'sum 4', '') |
| 69 | |
| 70 | self.issue1.reporter_id = 1002 |
| 71 | self.issue1.owner_id = 2002 |
| 72 | self.issue1.labels.extend(['A', 'B']) |
| 73 | self.issue1.derived_labels.extend(['C', 'D']) |
| 74 | |
| 75 | self.issue2.reporter_id = 2002 |
| 76 | self.issue2.labels.extend(['foo', 'bar']) |
| 77 | self.issue2.blocked_on_iids.extend( |
| 78 | [self.issue1.issue_id, self.issue3.issue_id]) |
| 79 | self.issue2.blocking_iids.extend( |
| 80 | [self.issue1.issue_id, self.issue4.issue_id]) |
| 81 | dref = tracker_pb2.DanglingIssueRef() |
| 82 | dref.project = 'codesite' |
| 83 | dref.issue_id = 5001 |
| 84 | self.issue2.dangling_blocking_refs.append(dref) |
| 85 | |
| 86 | self.issue3.reporter_id = 3002 |
| 87 | self.issue3.labels.extend(['Hot']) |
| 88 | |
| 89 | self.issue4.reporter_id = 3002 |
| 90 | self.issue4.labels.extend(['Foo', 'Bar']) |
| 91 | |
| 92 | self.restricted = _Issue('proj', 7, 'summary 7', '') |
| 93 | self.restricted.labels.extend([ |
| 94 | 'Restrict-View-Commit', 'Restrict-View-MyCustomPerm']) |
| 95 | self.restricted.derived_labels.extend([ |
| 96 | 'Restrict-AddIssueComment-Commit', 'Restrict-EditIssue-Commit', |
| 97 | 'Restrict-Action-NeededPerm']) |
| 98 | |
| 99 | self.users_by_id = { |
| 100 | 0: 'user 0', |
| 101 | 1002: 'user 1002', |
| 102 | 2002: 'user 2002', |
| 103 | 3002: 'user 3002', |
| 104 | 4002: 'user 4002', |
| 105 | } |
| 106 | |
| 107 | def CheckSimpleIssueView(self, config): |
| 108 | view1 = tracker_views.IssueView( |
| 109 | self.issue1, self.users_by_id, config) |
| 110 | self.assertEqual('not too long summary', view1.summary) |
| 111 | self.assertEqual('New', view1.status.name) |
| 112 | self.assertEqual('user 2002', view1.owner) |
| 113 | self.assertEqual('A', view1.labels[0].name) |
| 114 | self.assertEqual('B', view1.labels[1].name) |
| 115 | self.assertEqual('C', view1.derived_labels[0].name) |
| 116 | self.assertEqual('D', view1.derived_labels[1].name) |
| 117 | self.assertEqual([], view1.blocked_on) |
| 118 | self.assertEqual([], view1.blocking) |
| 119 | detail_url = '/p/%s%s?id=%d' % ( |
| 120 | self.issue1.project_name, urls.ISSUE_DETAIL, |
| 121 | self.issue1.local_id) |
| 122 | self.assertEqual(detail_url, view1.detail_relative_url) |
| 123 | return view1 |
| 124 | |
| 125 | def testSimpleIssueView(self): |
| 126 | config = tracker_pb2.ProjectIssueConfig() |
| 127 | view1 = self.CheckSimpleIssueView(config) |
| 128 | self.assertEqual('', view1.status.docstring) |
| 129 | |
| 130 | config.well_known_statuses.append(tracker_pb2.StatusDef( |
| 131 | status='New', status_docstring='Issue has not had review yet')) |
| 132 | view1 = self.CheckSimpleIssueView(config) |
| 133 | self.assertEqual('Issue has not had review yet', |
| 134 | view1.status.docstring) |
| 135 | self.assertIsNone(view1.restrictions.has_restrictions) |
| 136 | self.assertEqual('', view1.restrictions.view) |
| 137 | self.assertEqual('', view1.restrictions.add_comment) |
| 138 | self.assertEqual('', view1.restrictions.edit) |
| 139 | |
| 140 | def testIsOpen(self): |
| 141 | config = _MakeConfig() |
| 142 | view1 = tracker_views.IssueView( |
| 143 | self.issue1, self.users_by_id, config) |
| 144 | self.assertEqual(ezt.boolean(True), view1.is_open) |
| 145 | |
| 146 | self.issue1.status = 'Old' |
| 147 | view1 = tracker_views.IssueView( |
| 148 | self.issue1, self.users_by_id, config) |
| 149 | self.assertEqual(ezt.boolean(False), view1.is_open) |
| 150 | |
| 151 | def testIssueViewWithRestrictions(self): |
| 152 | view = tracker_views.IssueView( |
| 153 | self.restricted, self.users_by_id, _MakeConfig()) |
| 154 | self.assertTrue(view.restrictions.has_restrictions) |
| 155 | self.assertEqual('Commit and MyCustomPerm', view.restrictions.view) |
| 156 | self.assertEqual('Commit', view.restrictions.add_comment) |
| 157 | self.assertEqual('Commit', view.restrictions.edit) |
| 158 | self.assertEqual(['Restrict-Action-NeededPerm'], view.restrictions.other) |
| 159 | self.assertEqual('Restrict-View-Commit', view.labels[0].name) |
| 160 | self.assertTrue(view.labels[0].is_restrict) |
| 161 | |
| 162 | |
| 163 | class RestrictionsViewTest(unittest.TestCase): |
| 164 | pass # TODO(jrobbins): write tests |
| 165 | |
| 166 | |
| 167 | class AttachmentViewTest(unittest.TestCase): |
| 168 | |
| 169 | def setUp(self): |
| 170 | self.orig_sign_attachment_id = attachment_helpers.SignAttachmentID |
| 171 | attachment_helpers.SignAttachmentID = ( |
| 172 | lambda aid: 'signed_%d' % aid) |
| 173 | |
| 174 | def tearDown(self): |
| 175 | attachment_helpers.SignAttachmentID = self.orig_sign_attachment_id |
| 176 | |
| 177 | def MakeViewAndVerifyFields( |
| 178 | self, size, name, mimetype, expected_size_str, expect_viewable): |
| 179 | attach_pb = tracker_pb2.Attachment() |
| 180 | attach_pb.filesize = size |
| 181 | attach_pb.attachment_id = 12345 |
| 182 | attach_pb.filename = name |
| 183 | attach_pb.mimetype = mimetype |
| 184 | |
| 185 | view = tracker_views.AttachmentView(attach_pb, 'proj') |
| 186 | self.assertEqual('/images/paperclip.png', view.iconurl) |
| 187 | self.assertEqual(expected_size_str, view.filesizestr) |
| 188 | dl = 'attachment?aid=12345&signed_aid=signed_12345' |
| 189 | self.assertEqual(dl, view.downloadurl) |
| 190 | if expect_viewable: |
| 191 | self.assertEqual(dl + '&inline=1', view.url) |
| 192 | self.assertEqual(dl + '&inline=1&thumb=1', view.thumbnail_url) |
| 193 | else: |
| 194 | self.assertEqual(None, view.url) |
| 195 | self.assertEqual(None, view.thumbnail_url) |
| 196 | |
| 197 | def testNonImage(self): |
| 198 | self.MakeViewAndVerifyFields( |
| 199 | 123, 'file.ext', 'funky/bits', '123 bytes', False) |
| 200 | |
| 201 | def testViewableImage(self): |
| 202 | self.MakeViewAndVerifyFields( |
| 203 | 123, 'logo.gif', 'image/gif', '123 bytes', True) |
| 204 | |
| 205 | self.MakeViewAndVerifyFields( |
| 206 | 123, 'screenshot.jpg', 'image/jpeg', '123 bytes', True) |
| 207 | |
| 208 | def testHugeImage(self): |
| 209 | self.MakeViewAndVerifyFields( |
| 210 | 18 * 1024 * 1024, 'panorama.png', 'image/jpeg', '18.0 MB', False) |
| 211 | |
| 212 | def testViewableText(self): |
| 213 | name = 'hello.c' |
| 214 | attach_pb = tracker_pb2.Attachment() |
| 215 | attach_pb.filesize = 1234 |
| 216 | attach_pb.attachment_id = 12345 |
| 217 | attach_pb.filename = name |
| 218 | attach_pb.mimetype = 'text/plain' |
| 219 | view = tracker_views.AttachmentView(attach_pb, 'proj') |
| 220 | |
| 221 | view_url = '/p/proj/issues/attachmentText?aid=12345' |
| 222 | self.assertEqual(view_url, view.url) |
| 223 | |
| 224 | |
| 225 | class LogoViewTest(unittest.TestCase): |
| 226 | |
| 227 | def setUp(self): |
| 228 | self.mox = mox.Mox() |
| 229 | |
| 230 | def tearDown(self): |
| 231 | self.mox.UnsetStubs() |
| 232 | self.mox.ResetAll() |
| 233 | |
| 234 | def testProjectWithLogo(self): |
| 235 | bucket_name = 'testbucket' |
| 236 | logo_gcs_id = '123' |
| 237 | logo_file_name = 'logo.png' |
| 238 | project_pb = project_pb2.MakeProject( |
| 239 | 'testProject', logo_gcs_id=logo_gcs_id, logo_file_name=logo_file_name) |
| 240 | |
| 241 | self.mox.StubOutWithMock(app_identity, 'get_default_gcs_bucket_name') |
| 242 | app_identity.get_default_gcs_bucket_name().AndReturn(bucket_name) |
| 243 | |
| 244 | self.mox.StubOutWithMock(gcs_helpers, 'SignUrl') |
| 245 | gcs_helpers.SignUrl(bucket_name, |
| 246 | logo_gcs_id + '-thumbnail').AndReturn('signed/url') |
| 247 | gcs_helpers.SignUrl(bucket_name, logo_gcs_id).AndReturn('signed/url') |
| 248 | |
| 249 | self.mox.ReplayAll() |
| 250 | |
| 251 | view = tracker_views.LogoView(project_pb) |
| 252 | self.mox.VerifyAll() |
| 253 | self.assertEqual('logo.png', view.filename) |
| 254 | self.assertEqual('image/png', view.mimetype) |
| 255 | self.assertEqual('signed/url', view.thumbnail_url) |
| 256 | self.assertEqual( |
| 257 | 'signed/url&response-content-displacement=attachment%3B' |
| 258 | '+filename%3Dlogo.png', view.viewurl) |
| 259 | |
| 260 | def testProjectWithNoLogo(self): |
| 261 | project_pb = project_pb2.MakeProject('testProject') |
| 262 | view = tracker_views.LogoView(project_pb) |
| 263 | self.assertEqual('', view.thumbnail_url) |
| 264 | self.assertEqual('', view.viewurl) |
| 265 | |
| 266 | |
| 267 | class AmendmentViewTest(unittest.TestCase): |
| 268 | pass # TODO(jrobbins): write tests |
| 269 | |
| 270 | |
| 271 | class ComponentDefViewTest(unittest.TestCase): |
| 272 | def setUp(self): |
| 273 | self.services = service_manager.Services( |
| 274 | user=fake.UserService(), |
| 275 | config=fake.ConfigService()) |
| 276 | self.services.user.TestAddUser('admin@example.com', 111) |
| 277 | self.services.user.TestAddUser('cc@example.com', 222) |
| 278 | self.users_by_id = framework_views.MakeAllUserViews( |
| 279 | 'cnxn', self.services.user, [111, 222]) |
| 280 | self.services.config.TestAddLabelsDict({'Hot': 1, 'Cold': 2}) |
| 281 | self.cd = tracker_bizobj.MakeComponentDef( |
| 282 | 10, 789, 'UI', 'User interface', False, |
| 283 | [111], [222], 0, 111, label_ids=[1, 2]) |
| 284 | |
| 285 | def testRootComponent(self): |
| 286 | view = tracker_views.ComponentDefView( |
| 287 | 'cnxn', self.services, self.cd, self.users_by_id) |
| 288 | self.assertEqual('', view.parent_path) |
| 289 | self.assertEqual('UI', view.leaf_name) |
| 290 | self.assertEqual('User interface', view.docstring_short) |
| 291 | self.assertEqual('admin@example.com', view.admins[0].email) |
| 292 | self.assertEqual(['Hot', 'Cold'], view.labels) |
| 293 | self.assertEqual('all toplevel active ', view.classes) |
| 294 | |
| 295 | def testNestedComponent(self): |
| 296 | self.cd.path = 'UI>Dialogs>Print' |
| 297 | view = tracker_views.ComponentDefView( |
| 298 | 'cnxn', self.services, self.cd, self.users_by_id) |
| 299 | self.assertEqual('UI>Dialogs', view.parent_path) |
| 300 | self.assertEqual('Print', view.leaf_name) |
| 301 | self.assertEqual('User interface', view.docstring_short) |
| 302 | self.assertEqual('admin@example.com', view.admins[0].email) |
| 303 | self.assertEqual(['Hot', 'Cold'], view.labels) |
| 304 | self.assertEqual('all active ', view.classes) |
| 305 | |
| 306 | |
| 307 | class ComponentValueTest(unittest.TestCase): |
| 308 | pass # TODO(jrobbins): write tests |
| 309 | |
| 310 | |
| 311 | class FieldValueViewTest(unittest.TestCase): |
| 312 | |
| 313 | def setUp(self): |
| 314 | self.config = tracker_pb2.ProjectIssueConfig() |
| 315 | self.estdays_fd = tracker_bizobj.MakeFieldDef( |
| 316 | 1, 789, 'EstDays', tracker_pb2.FieldTypes.INT_TYPE, None, |
| 317 | None, False, False, False, 3, 99, None, False, None, None, |
| 318 | None, 'no_action', 'descriptive docstring', False, approval_id=None, |
| 319 | is_phase_field=False) |
| 320 | self.designdoc_fd = tracker_bizobj.MakeFieldDef( |
| 321 | 2, 789, 'DesignDoc', tracker_pb2.FieldTypes.STR_TYPE, 'Enhancement', |
| 322 | None, False, False, False, None, None, None, False, None, None, |
| 323 | None, 'no_action', 'descriptive docstring', False, approval_id=None, |
| 324 | is_phase_field=False) |
| 325 | self.mtarget_fd = tracker_bizobj.MakeFieldDef( |
| 326 | 3, 789, 'M-Target', tracker_pb2.FieldTypes.INT_TYPE, 'Enhancement', |
| 327 | None, False, False, False, None, None, None, False, None, None, |
| 328 | None, 'no_action', 'doc doc', False, approval_id=None, |
| 329 | is_phase_field=True) |
| 330 | self.config.field_defs = [self.estdays_fd, self.designdoc_fd] |
| 331 | |
| 332 | def testNoValues(self): |
| 333 | """We can create a FieldValueView with no values.""" |
| 334 | values = [] |
| 335 | derived_values = [] |
| 336 | estdays_fvv = tracker_views.FieldValueView( |
| 337 | self.estdays_fd, self.config, values, derived_values, ['defect'], |
| 338 | phase_name='Gate') |
| 339 | self.assertEqual('EstDays', estdays_fvv.field_def.field_name) |
| 340 | self.assertEqual(3, estdays_fvv.field_def.min_value) |
| 341 | self.assertEqual(99, estdays_fvv.field_def.max_value) |
| 342 | self.assertEqual([], estdays_fvv.values) |
| 343 | self.assertEqual([], estdays_fvv.derived_values) |
| 344 | |
| 345 | def testSomeValues(self): |
| 346 | """We can create a FieldValueView with some values.""" |
| 347 | values = [template_helpers.EZTItem(val=12, docstring=None, idx=0)] |
| 348 | derived_values = [template_helpers.EZTItem(val=88, docstring=None, idx=0)] |
| 349 | estdays_fvv = tracker_views.FieldValueView( |
| 350 | self.estdays_fd, self.config, values, derived_values, ['defect']) |
| 351 | self.assertEqual(self.estdays_fd, estdays_fvv.field_def.field_def) |
| 352 | self.assertTrue(estdays_fvv.is_editable) |
| 353 | self.assertEqual(values, estdays_fvv.values) |
| 354 | self.assertEqual(derived_values, estdays_fvv.derived_values) |
| 355 | self.assertEqual('', estdays_fvv.phase_name) |
| 356 | self.assertEqual(ezt.boolean(False), estdays_fvv.field_def.is_phase_field) |
| 357 | |
| 358 | def testApplicability(self): |
| 359 | """We know whether a field should show an editing widget.""" |
| 360 | # Not the right type and has no values. |
| 361 | designdoc_fvv = tracker_views.FieldValueView( |
| 362 | self.designdoc_fd, self.config, [], [], ['defect']) |
| 363 | self.assertFalse(designdoc_fvv.applicable) |
| 364 | self.assertEqual('', designdoc_fvv.phase_name) |
| 365 | self.assertEqual(ezt.boolean(False), designdoc_fvv.field_def.is_phase_field) |
| 366 | |
| 367 | # Has a value. |
| 368 | designdoc_fvv = tracker_views.FieldValueView( |
| 369 | self.designdoc_fd, self.config, ['fake value item'], [], ['defect']) |
| 370 | self.assertTrue(designdoc_fvv.applicable) |
| 371 | |
| 372 | # Derived values don't cause editing fields to display. |
| 373 | designdoc_fvv = tracker_views.FieldValueView( |
| 374 | self.designdoc_fd, self.config, [], ['fake value item'], ['defect']) |
| 375 | self.assertFalse(designdoc_fvv.applicable) |
| 376 | |
| 377 | # Applicable to this type of issue. |
| 378 | designdoc_fvv = tracker_views.FieldValueView( |
| 379 | self.designdoc_fd, self.config, [], [], ['enhancement']) |
| 380 | self.assertTrue(designdoc_fvv.applicable) |
| 381 | |
| 382 | # Applicable to some issues in a bulk edit. |
| 383 | designdoc_fvv = tracker_views.FieldValueView( |
| 384 | self.designdoc_fd, self.config, [], [], |
| 385 | ['defect', 'task', 'enhancement']) |
| 386 | self.assertTrue(designdoc_fvv.applicable) |
| 387 | |
| 388 | # Applicable to all issues. |
| 389 | estdays_fvv = tracker_views.FieldValueView( |
| 390 | self.estdays_fd, self.config, [], [], ['enhancement']) |
| 391 | self.assertTrue(estdays_fvv.applicable) |
| 392 | |
| 393 | # Explicitly set to be applicable when showing bounce values. |
| 394 | designdoc_fvv = tracker_views.FieldValueView( |
| 395 | self.designdoc_fd, self.config, [], [], ['defect'], |
| 396 | applicable=True) |
| 397 | self.assertTrue(designdoc_fvv.applicable) |
| 398 | |
| 399 | def testDisplay(self): |
| 400 | """We know when a value (or --) should be shown in the metadata column.""" |
| 401 | # Not the right type and has no values. |
| 402 | designdoc_fvv = tracker_views.FieldValueView( |
| 403 | self.designdoc_fd, self.config, [], [], ['defect']) |
| 404 | self.assertFalse(designdoc_fvv.display) |
| 405 | |
| 406 | # Has a value. |
| 407 | designdoc_fvv = tracker_views.FieldValueView( |
| 408 | self.designdoc_fd, self.config, ['fake value item'], [], ['defect']) |
| 409 | self.assertTrue(designdoc_fvv.display) |
| 410 | |
| 411 | # Has a derived value. |
| 412 | designdoc_fvv = tracker_views.FieldValueView( |
| 413 | self.designdoc_fd, self.config, [], ['fake value item'], ['defect']) |
| 414 | self.assertTrue(designdoc_fvv.display) |
| 415 | |
| 416 | # Applicable to this type of issue, it will show "--". |
| 417 | designdoc_fvv = tracker_views.FieldValueView( |
| 418 | self.designdoc_fd, self.config, [], [], ['enhancement']) |
| 419 | self.assertTrue(designdoc_fvv.display) |
| 420 | |
| 421 | # Applicable to all issues, it will show "--". |
| 422 | estdays_fvv = tracker_views.FieldValueView( |
| 423 | self.estdays_fd, self.config, [], [], ['enhancement']) |
| 424 | self.assertTrue(estdays_fvv.display) |
| 425 | |
| 426 | def testPhaseField(self): |
| 427 | mtarget_fvv = tracker_views.FieldValueView( |
| 428 | self.mtarget_fd, self.config, [], [], [], phase_name='Stage') |
| 429 | self.assertEqual('Stage', mtarget_fvv.phase_name) |
| 430 | self.assertEqual(ezt.boolean(True), mtarget_fvv.field_def.is_phase_field) |
| 431 | |
| 432 | |
| 433 | class FVVFunctionsTest(unittest.TestCase): |
| 434 | |
| 435 | def setUp(self): |
| 436 | self.config = tracker_pb2.ProjectIssueConfig() |
| 437 | self.estdays_fd = tracker_bizobj.MakeFieldDef( |
| 438 | 1, 789, 'EstDays', tracker_pb2.FieldTypes.INT_TYPE, None, |
| 439 | None, False, False, False, 3, 99, None, False, None, None, |
| 440 | None, 'no_action', 'descriptive docstring', False, None, False) |
| 441 | self.os_fd = tracker_bizobj.MakeFieldDef( |
| 442 | 2, 789, 'OS', tracker_pb2.FieldTypes.ENUM_TYPE, |
| 443 | 'Enhancement', None, False, False, False, None, None, None, |
| 444 | False, None, None, None, 'no_action', 'descriptive docstring', |
| 445 | False, None, False) |
| 446 | self.milestone_fd = tracker_bizobj.MakeFieldDef( |
| 447 | 3, 789, 'Launch-Milestone', tracker_pb2.FieldTypes.ENUM_TYPE, |
| 448 | 'Enhancement', None, False, False, False, None, None, None, |
| 449 | False, None, None, None, 'no_action', 'descriptive docstring', |
| 450 | False, None, False) |
| 451 | self.config.field_defs = [self.estdays_fd, self.os_fd, self.milestone_fd] |
| 452 | self.config.well_known_labels = [ |
| 453 | tracker_pb2.LabelDef( |
| 454 | label='Priority-High', label_docstring='Must be resolved'), |
| 455 | tracker_pb2.LabelDef( |
| 456 | label='Priority-Low', label_docstring='Can be slipped'), |
| 457 | ] |
| 458 | |
| 459 | def testPrecomputeInfoForValueViews_NoValues(self): |
| 460 | """We can precompute info needed for an issue with no fields or labels.""" |
| 461 | labels = [] |
| 462 | derived_labels = [] |
| 463 | field_values = [] |
| 464 | phases = [] |
| 465 | precomp_view_info = tracker_views._PrecomputeInfoForValueViews( |
| 466 | labels, derived_labels, field_values, self.config, phases) |
| 467 | (labels_by_prefix, der_labels_by_prefix, field_values_by_id, |
| 468 | label_docs, phases_by_name) = precomp_view_info |
| 469 | self.assertEqual({}, labels_by_prefix) |
| 470 | self.assertEqual({}, der_labels_by_prefix) |
| 471 | self.assertEqual({}, field_values_by_id) |
| 472 | self.assertEqual( |
| 473 | {'priority-high': 'Must be resolved', |
| 474 | 'priority-low': 'Can be slipped'}, |
| 475 | label_docs) |
| 476 | self.assertEqual({}, phases_by_name) |
| 477 | |
| 478 | def testPrecomputeInfoForValueViews_SomeValues(self): |
| 479 | """We can precompute info needed for an issue with fields and labels.""" |
| 480 | labels = ['Priority-Low', 'GoodFirstBug', 'Feature-UI', 'Feature-Installer', |
| 481 | 'Launch-Milestone-66'] |
| 482 | derived_labels = ['OS-Windows', 'OS-Linux'] |
| 483 | field_values = [ |
| 484 | tracker_bizobj.MakeFieldValue(1, 5, None, None, None, None, False), |
| 485 | ] |
| 486 | phase_1 = tracker_pb2.Phase(phase_id=1, name='Stable') |
| 487 | phase_2 = tracker_pb2.Phase(phase_id=2, name='Beta') |
| 488 | phase_3 = tracker_pb2.Phase(phase_id=3, name='stable') |
| 489 | precomp_view_info = tracker_views._PrecomputeInfoForValueViews( |
| 490 | labels, derived_labels, field_values, self.config, |
| 491 | phases=[phase_1, phase_2, phase_3]) |
| 492 | (labels_by_prefix, der_labels_by_prefix, field_values_by_id, |
| 493 | _label_docs, phases_by_name) = precomp_view_info |
| 494 | self.assertEqual( |
| 495 | {'priority': ['Low'], |
| 496 | 'feature': ['UI', 'Installer'], |
| 497 | 'launch-milestone': ['66']}, |
| 498 | labels_by_prefix) |
| 499 | self.assertEqual( |
| 500 | {'os': ['Windows', 'Linux']}, |
| 501 | der_labels_by_prefix) |
| 502 | self.assertEqual( |
| 503 | {1: field_values}, |
| 504 | field_values_by_id) |
| 505 | self.assertEqual( |
| 506 | {'stable': [phase_1, phase_3], |
| 507 | 'beta': [phase_2]}, |
| 508 | phases_by_name) |
| 509 | |
| 510 | def testMakeAllFieldValueViews(self): |
| 511 | labels = ['Priority-Low', 'GoodFirstBug', 'Feature-UI', 'Feature-Installer', |
| 512 | 'Launch-Milestone-66'] |
| 513 | derived_labels = ['OS-Windows', 'OS-Linux'] |
| 514 | self.config.field_defs.append(tracker_bizobj.MakeFieldDef( |
| 515 | 4, 789, 'UIMocks', tracker_pb2.FieldTypes.URL_TYPE, |
| 516 | 'Enhancement', None, False, False, False, None, None, None, |
| 517 | False, None, None, None, 'no_action', 'descriptive docstring', |
| 518 | False, approval_id=23, is_phase_field=False)) |
| 519 | self.config.field_defs.append(tracker_bizobj.MakeFieldDef( |
| 520 | 5, 789, 'LegalFAQs', tracker_pb2.FieldTypes.URL_TYPE, |
| 521 | 'Enhancement', None, False, False, False, None, None, None, |
| 522 | False, None, None, None, 'no_action', 'descriptive docstring', |
| 523 | False, approval_id=26, is_phase_field=False)) |
| 524 | self.config.field_defs.append(tracker_bizobj.MakeFieldDef( |
| 525 | 23, 789, 'Legal', tracker_pb2.FieldTypes.APPROVAL_TYPE, |
| 526 | 'Enhancement', None, False, False, False, None, None, None, |
| 527 | False, None, None, None, 'no_action', 'descriptive docstring', |
| 528 | False, approval_id=None, is_phase_field=False)) |
| 529 | self.config.field_defs.append(tracker_bizobj.MakeFieldDef( |
| 530 | 26, 789, 'UI', tracker_pb2.FieldTypes.APPROVAL_TYPE, |
| 531 | 'Enhancement', None, False, False, False, None, None, None, |
| 532 | False, None, None, None, 'no_action', 'descriptive docstring', |
| 533 | False, approval_id=None, is_phase_field=False)) |
| 534 | self.config.field_defs.append(tracker_bizobj.MakeFieldDef( |
| 535 | 27, 789, 'M-Target', tracker_pb2.FieldTypes.INT_TYPE, |
| 536 | 'Enhancement', None, False, False, False, None, None, None, |
| 537 | False, None, None, None, 'no_action', 'descriptive docstring', |
| 538 | False, approval_id=None, is_phase_field=True)) |
| 539 | field_values = [ |
| 540 | tracker_bizobj.MakeFieldValue(1, 5, None, None, None, None, False), |
| 541 | tracker_bizobj.MakeFieldValue( |
| 542 | 27, 74, None, None, None, None, False, phase_id=3), |
| 543 | # phase_id=4 does not belong to any of the phases given below. |
| 544 | # this field value should not show up in the views. |
| 545 | tracker_bizobj.MakeFieldValue( |
| 546 | 27, 79, None, None, None, None, False, phase_id=4), |
| 547 | ] |
| 548 | users_by_id = {} |
| 549 | phase_1 = tracker_pb2.Phase(phase_id=1, name='Stable') |
| 550 | phase_2 = tracker_pb2.Phase(phase_id=2, name='Beta') |
| 551 | phase_3 = tracker_pb2.Phase(phase_id=3, name='stable') |
| 552 | fvvs = tracker_views.MakeAllFieldValueViews( |
| 553 | self.config, labels, derived_labels, field_values, users_by_id, |
| 554 | parent_approval_ids=[23], phases=[phase_1, phase_2, phase_3]) |
| 555 | self.assertEqual(9, len(fvvs)) |
| 556 | # Values are sorted by (applicable_type, field_name). |
| 557 | logging.info([fv.field_name for fv in fvvs]) |
| 558 | (estdays_fvv, launch_milestone_fvv, legal_fvv, legal_faq_fvv, |
| 559 | beta_mtarget_fvv, stable_mtarget_fvv, os_fvv, ui_fvv, ui_mocks_fvv) = fvvs |
| 560 | self.assertEqual('EstDays', estdays_fvv.field_name) |
| 561 | self.assertEqual(1, len(estdays_fvv.values)) |
| 562 | self.assertEqual(0, len(estdays_fvv.derived_values)) |
| 563 | self.assertEqual('Launch-Milestone', launch_milestone_fvv.field_name) |
| 564 | self.assertEqual(1, len(launch_milestone_fvv.values)) |
| 565 | self.assertEqual(0, len(launch_milestone_fvv.derived_values)) |
| 566 | self.assertEqual('OS', os_fvv.field_name) |
| 567 | self.assertEqual(0, len(os_fvv.values)) |
| 568 | self.assertEqual(2, len(os_fvv.derived_values)) |
| 569 | self.assertEqual(ui_mocks_fvv.field_name, 'UIMocks') |
| 570 | self.assertEqual(ui_mocks_fvv.phase_name, '') |
| 571 | self.assertTrue(ui_mocks_fvv.applicable) |
| 572 | self.assertEqual(legal_faq_fvv.field_name, 'LegalFAQs') |
| 573 | self.assertFalse(legal_faq_fvv.applicable) |
| 574 | self.assertFalse(legal_fvv.applicable) |
| 575 | self.assertFalse(ui_fvv.applicable) |
| 576 | self.assertEqual('M-Target', stable_mtarget_fvv.field_name) |
| 577 | self.assertEqual('stable', stable_mtarget_fvv.phase_name) |
| 578 | self.assertEqual(1, len(stable_mtarget_fvv.values)) |
| 579 | self.assertEqual(74, stable_mtarget_fvv.values[0].val) |
| 580 | self.assertEqual(0, len(stable_mtarget_fvv.derived_values)) |
| 581 | self.assertEqual('M-Target', beta_mtarget_fvv.field_name) |
| 582 | self.assertEqual('beta', beta_mtarget_fvv.phase_name) |
| 583 | self.assertEqual(0, len(beta_mtarget_fvv.values)) |
| 584 | self.assertEqual(0, len(beta_mtarget_fvv.values)) |
| 585 | |
| 586 | def testMakeFieldValueView(self): |
| 587 | pass # Covered by testMakeAllFieldValueViews() |
| 588 | |
| 589 | def testMakeFieldValueItemsTest(self): |
| 590 | pass # Covered by testMakeAllFieldValueViews() |
| 591 | |
| 592 | def testMakeBounceFieldValueViews(self): |
| 593 | config = tracker_pb2.ProjectIssueConfig() |
| 594 | fd = tracker_pb2.FieldDef( |
| 595 | field_id=3, field_type=tracker_pb2.FieldTypes.INT_TYPE, |
| 596 | applicable_type='', field_name='EstDays') |
| 597 | phase_fd = tracker_pb2.FieldDef( |
| 598 | field_id=4, field_type=tracker_pb2.FieldTypes.INT_TYPE, |
| 599 | applicable_type='', field_name='Gump') |
| 600 | config.field_defs = [fd, |
| 601 | phase_fd, |
| 602 | tracker_pb2.FieldDef( |
| 603 | field_id=5, field_type=tracker_pb2.FieldTypes.STR_TYPE) |
| 604 | ] |
| 605 | parsed_fvs = {3: [455]} |
| 606 | parsed_phase_fvs = { |
| 607 | 4: {'stable': [73, 74], 'beta': [8], 'beta-exp': [75]}, |
| 608 | } |
| 609 | fvs = tracker_views.MakeBounceFieldValueViews( |
| 610 | parsed_fvs, parsed_phase_fvs, config) |
| 611 | |
| 612 | self.assertEqual(len(fvs), 4) |
| 613 | |
| 614 | estdays_ezt_fv = template_helpers.EZTItem(val=455, docstring='', idx=0) |
| 615 | expected = tracker_views.FieldValueView( |
| 616 | fd, config, [estdays_ezt_fv], [], []) |
| 617 | self.assertEqual(fvs[0].field_name, expected.field_name) |
| 618 | self.assertEqual(fvs[0].values[0].val, expected.values[0].val) |
| 619 | self.assertEqual(fvs[0].values[0].idx, expected.values[0].idx) |
| 620 | self.assertTrue(fvs[0].applicable) |
| 621 | |
| 622 | self.assertEqual(fvs[1].field_name, phase_fd.field_name) |
| 623 | self.assertEqual(fvs[2].field_name, phase_fd.field_name) |
| 624 | self.assertEqual(fvs[3].field_name, phase_fd.field_name) |
| 625 | |
| 626 | fd.approval_id = 23 |
| 627 | config.field_defs = [fd, |
| 628 | tracker_pb2.FieldDef( |
| 629 | field_id=23, field_name='Legal', |
| 630 | field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE)] |
| 631 | fvs = tracker_views.MakeBounceFieldValueViews(parsed_fvs, {}, config) |
| 632 | self.assertTrue(fvs[0].applicable) |
| 633 | |
| 634 | |
| 635 | class ConvertLabelsToFieldValuesTest(unittest.TestCase): |
| 636 | |
| 637 | def testConvertLabelsToFieldValues_NoLabels(self): |
| 638 | result = tracker_views._ConvertLabelsToFieldValues( |
| 639 | [], 'opsys', {}) |
| 640 | self.assertEqual([], result) |
| 641 | |
| 642 | def testConvertLabelsToFieldValues_NoMatch(self): |
| 643 | result = tracker_views._ConvertLabelsToFieldValues( |
| 644 | [], 'opsys', {}) |
| 645 | self.assertEqual([], result) |
| 646 | |
| 647 | def testConvertLabelsToFieldValues_HasMatch(self): |
| 648 | result = tracker_views._ConvertLabelsToFieldValues( |
| 649 | ['OSX'], 'opsys', {}) |
| 650 | self.assertEqual(1, len(result)) |
| 651 | self.assertEqual('OSX', result[0].val) |
| 652 | self.assertEqual('', result[0].docstring) |
| 653 | |
| 654 | result = tracker_views._ConvertLabelsToFieldValues( |
| 655 | ['OSX', 'All'], 'opsys', {'opsys-all': 'Happens everywhere'}) |
| 656 | self.assertEqual(2, len(result)) |
| 657 | self.assertEqual('OSX', result[0].val) |
| 658 | self.assertEqual('', result[0].docstring) |
| 659 | self.assertEqual('All', result[1].val) |
| 660 | self.assertEqual('Happens everywhere', result[1].docstring) |
| 661 | |
| 662 | |
| 663 | class FieldDefViewTest(unittest.TestCase): |
| 664 | |
| 665 | def setUp(self): |
| 666 | self.approval_fd = tracker_bizobj.MakeFieldDef( |
| 667 | 1, 789, 'LaunchApproval', tracker_pb2.FieldTypes.APPROVAL_TYPE, None, |
| 668 | None, True, True, False, 3, 99, None, False, None, None, |
| 669 | None, 'no_action', 'descriptive docstring', False, None, False) |
| 670 | |
| 671 | self.approval_def = tracker_pb2.ApprovalDef( |
| 672 | approval_id=1, approver_ids=[111], survey='question?') |
| 673 | |
| 674 | self.field_def = tracker_bizobj.MakeFieldDef( |
| 675 | 2, 789, 'AffectedUsers', tracker_pb2.FieldTypes.INT_TYPE, None, |
| 676 | None, True, True, False, 3, 99, None, False, None, None, |
| 677 | None, 'no_action', 'descriptive docstring', False, 1, False) |
| 678 | |
| 679 | self.field_def.admin_ids = [222] |
| 680 | self.field_def.editor_ids = [111, 333] |
| 681 | |
| 682 | def testFieldDefView_Normal(self): |
| 683 | config = _MakeConfig() |
| 684 | config.field_defs.append(self.approval_fd) |
| 685 | config.approval_defs.append(self.approval_def) |
| 686 | |
| 687 | user_view_1 = framework_views.StuffUserView(111, 'uv1@example.com', False) |
| 688 | user_view_2 = framework_views.StuffUserView(222, 'uv2@example.com', False) |
| 689 | user_view_3 = framework_views.StuffUserView(333, 'uv3@example.com', False) |
| 690 | user_views = {111: user_view_1, 222: user_view_2, 333: user_view_3} |
| 691 | view = tracker_views.FieldDefView( |
| 692 | self.field_def, config, user_views=user_views) |
| 693 | |
| 694 | self.assertEqual('AffectedUsers', view.field_name) |
| 695 | self.assertEqual(self.field_def, view.field_def) |
| 696 | self.assertEqual('descriptive docstring', view.docstring_short) |
| 697 | self.assertEqual('INT_TYPE', view.type_name) |
| 698 | self.assertEqual([], view.choices) |
| 699 | self.assertEqual('required', view.importance) |
| 700 | self.assertEqual(3, view.min_value) |
| 701 | self.assertEqual(99, view.max_value) |
| 702 | self.assertEqual('no_action', view.date_action_str) |
| 703 | self.assertEqual(view.approval_id, 1) |
| 704 | self.assertEqual(view.is_approval_subfield, ezt.boolean(True)) |
| 705 | self.assertEqual(view.approvers, []) |
| 706 | self.assertEqual(view.survey, '') |
| 707 | self.assertEqual(view.survey_questions, []) |
| 708 | self.assertEqual(len(view.admins), 1) |
| 709 | self.assertEqual(len(view.editors), 2) |
| 710 | self.assertIsNone(view.is_phase_field) |
| 711 | self.assertIsNone(view.is_restricted_field) |
| 712 | |
| 713 | def testFieldDefView_Approval(self): |
| 714 | config = _MakeConfig() |
| 715 | approver_view = framework_views.StuffUserView( |
| 716 | 111, 'shouldnotmatter@ch.org', False) |
| 717 | user_views = {111: approver_view} |
| 718 | |
| 719 | view = tracker_views.FieldDefView( |
| 720 | self.approval_fd, config, |
| 721 | user_views= user_views, approval_def=self.approval_def) |
| 722 | self.assertEqual(view.approvers, [approver_view]) |
| 723 | self.assertEqual(view.survey, self.approval_def.survey) |
| 724 | self.assertEqual(view.survey_questions, [view.survey]) |
| 725 | |
| 726 | self.approval_def.survey = None |
| 727 | view = tracker_views.FieldDefView( |
| 728 | self.approval_fd, config, |
| 729 | user_views= user_views, approval_def=self.approval_def) |
| 730 | self.assertEqual(view.survey, '') |
| 731 | self.assertEqual(view.survey_questions, []) |
| 732 | |
| 733 | self.approval_def.survey = 'Q1\nQ2\nQ3' |
| 734 | view = tracker_views.FieldDefView( |
| 735 | self.approval_fd, config, |
| 736 | user_views= user_views, approval_def=self.approval_def) |
| 737 | self.assertEqual(view.survey, self.approval_def.survey) |
| 738 | self.assertEqual(view.survey_questions, ['Q1', 'Q2', 'Q3']) |
| 739 | |
| 740 | |
| 741 | class IssueTemplateViewTest(unittest.TestCase): |
| 742 | pass # TODO(jrobbins): write tests |
| 743 | |
| 744 | |
| 745 | class MakeFieldUserViewsTest(unittest.TestCase): |
| 746 | pass # TODO(jrobbins): write tests |
| 747 | |
| 748 | |
| 749 | class ConfigViewTest(unittest.TestCase): |
| 750 | pass # TODO(jrobbins): write tests |
| 751 | |
| 752 | |
| 753 | class ConfigFunctionsTest(unittest.TestCase): |
| 754 | |
| 755 | def setUp(self): |
| 756 | self.config = tracker_bizobj.MakeDefaultProjectIssueConfig(768) |
| 757 | |
| 758 | def testStatusDefsAsText(self): |
| 759 | open_text, closed_text = tracker_views.StatusDefsAsText(self.config) |
| 760 | |
| 761 | for wks in tracker_constants.DEFAULT_WELL_KNOWN_STATUSES: |
| 762 | status, doc, means_open, _deprecated = wks |
| 763 | if means_open: |
| 764 | self.assertIn(status, open_text) |
| 765 | self.assertIn(doc, open_text) |
| 766 | else: |
| 767 | self.assertIn(status, closed_text) |
| 768 | self.assertIn(doc, closed_text) |
| 769 | |
| 770 | self.assertEqual( |
| 771 | len(tracker_constants.DEFAULT_WELL_KNOWN_STATUSES), |
| 772 | len(open_text.split('\n')) + len(closed_text.split('\n'))) |
| 773 | |
| 774 | def testLabelDefsAsText(self): |
| 775 | # Note: Day-Monday will not be part of the result because it is masked. |
| 776 | self.config.field_defs.append(tracker_pb2.FieldDef( |
| 777 | field_id=1, field_name='Day', |
| 778 | field_type=tracker_pb2.FieldTypes.ENUM_TYPE)) |
| 779 | self.config.well_known_labels.append(tracker_pb2.LabelDef( |
| 780 | label='Day-Monday')) |
| 781 | labels_text = tracker_views.LabelDefsAsText(self.config) |
| 782 | |
| 783 | for wkl in tracker_constants.DEFAULT_WELL_KNOWN_LABELS: |
| 784 | label, doc, _deprecated = wkl |
| 785 | self.assertIn(label, labels_text) |
| 786 | self.assertIn(doc, labels_text) |
| 787 | self.assertEqual( |
| 788 | len(tracker_constants.DEFAULT_WELL_KNOWN_LABELS), |
| 789 | len(labels_text.split('\n'))) |