@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add config validation for task status config

Summary: Ref T1812. This still doesn't expose configuration to the user, but adds validation for it.

Test Plan: Added a pile of unit tests.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T1812

Differential Revision: https://secure.phabricator.com/D8584

+252 -1
+2
src/__phutil_library_map__.php
··· 898 898 'ManiphestTaskQuery' => 'applications/maniphest/query/ManiphestTaskQuery.php', 899 899 'ManiphestTaskSearchEngine' => 'applications/maniphest/query/ManiphestTaskSearchEngine.php', 900 900 'ManiphestTaskStatus' => 'applications/maniphest/constants/ManiphestTaskStatus.php', 901 + 'ManiphestTaskStatusTestCase' => 'applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php', 901 902 'ManiphestTaskSubscriber' => 'applications/maniphest/storage/ManiphestTaskSubscriber.php', 902 903 'ManiphestTransaction' => 'applications/maniphest/storage/ManiphestTransaction.php', 903 904 'ManiphestTransactionComment' => 'applications/maniphest/storage/ManiphestTransactionComment.php', ··· 3549 3550 'ManiphestTaskQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3550 3551 'ManiphestTaskSearchEngine' => 'PhabricatorApplicationSearchEngine', 3551 3552 'ManiphestTaskStatus' => 'ManiphestConstants', 3553 + 'ManiphestTaskStatusTestCase' => 'PhabricatorTestCase', 3552 3554 'ManiphestTaskSubscriber' => 'ManiphestDAO', 3553 3555 'ManiphestTransaction' => 'PhabricatorApplicationTransaction', 3554 3556 'ManiphestTransactionComment' => 'PhabricatorApplicationTransactionComment',
+136 -1
src/applications/maniphest/constants/ManiphestTaskStatus.php
··· 1 1 <?php 2 2 3 + /** 4 + * @task validate Configuration Validation 5 + */ 3 6 final class ManiphestTaskStatus extends ManiphestConstants { 4 7 5 8 const STATUS_OPEN = 'open'; ··· 151 154 } 152 155 153 156 private static function getSpecialStatus($special) { 154 - foreach (self::getEnabledStatusMap() as $const => $status) { 157 + foreach (self::getStatusConfig() as $const => $status) { 155 158 if (idx($status, 'special') == $special) { 156 159 return $const; 157 160 } ··· 249 252 } 250 253 251 254 return $default; 255 + } 256 + 257 + 258 + /* -( Configuration Validation )------------------------------------------- */ 259 + 260 + 261 + /** 262 + * @task validate 263 + */ 264 + public static function isValidStatusConstant($constant) { 265 + if (strlen($constant) > 12) { 266 + return false; 267 + } 268 + if (!preg_match('/^[a-z0-9]+\z/', $constant)) { 269 + return false; 270 + } 271 + return true; 272 + } 273 + 274 + 275 + /** 276 + * @task validate 277 + */ 278 + public static function validateConfiguration(array $config) { 279 + foreach ($config as $key => $value) { 280 + if (!self::isValidStatusConstant($key)) { 281 + throw new Exception( 282 + pht( 283 + 'Key "%s" is not a valid status constant. Status constants must '. 284 + 'be 1-12 characters long and contain only lowercase letters (a-z) '. 285 + 'and digits (0-9). For example, "%s" or "%s" are reasonable '. 286 + 'choices.', 287 + $key, 288 + 'open', 289 + 'closed')); 290 + } 291 + if (!is_array($value)) { 292 + throw new Exception( 293 + pht( 294 + 'Value for key "%s" should be a dictionary.', 295 + $key)); 296 + } 297 + 298 + PhutilTypeSpec::checkMap( 299 + $value, 300 + array( 301 + 'name' => 'string', 302 + 'name.full' => 'optional string', 303 + 'name.action' => 'optional string', 304 + 'closed' => 'optional bool', 305 + 'special' => 'optional string', 306 + 'transaction.icon' => 'optional string', 307 + 'transaction.color' => 'optional string', 308 + 'silly' => 'optional bool', 309 + 'prefixes' => 'optional list<string>', 310 + 'suffixes' => 'optional list<string>', 311 + )); 312 + } 313 + 314 + $special_map = array(); 315 + foreach ($config as $key => $value) { 316 + $special = idx($value, 'special'); 317 + if (!$special) { 318 + continue; 319 + } 320 + 321 + if (isset($special_map[$special])) { 322 + throw new Exception( 323 + pht( 324 + 'Configuration has two statuses both marked with the special '. 325 + 'attribute "%s" ("%s" and "%s"). There should be only one.', 326 + $special, 327 + $special_map[$special], 328 + $key)); 329 + } 330 + 331 + switch ($special) { 332 + case self::SPECIAL_DEFAULT: 333 + if (!empty($value['closed'])) { 334 + throw new Exception( 335 + pht( 336 + 'Status "%s" is marked as default, but it is a closed '. 337 + 'status. The default status should be an open status.', 338 + $key)); 339 + } 340 + break; 341 + case self::SPECIAL_CLOSED: 342 + if (empty($value['closed'])) { 343 + throw new Exception( 344 + pht( 345 + 'Status "%s" is marked as the default status for closing '. 346 + 'tasks, but is not a closed status. It should be a closed '. 347 + 'status.', 348 + $key)); 349 + } 350 + break; 351 + case self::SPECIAL_DUPLICATE: 352 + if (empty($value['closed'])) { 353 + throw new Exception( 354 + pht( 355 + 'Status "%s" is marked as the status for closing tasks as '. 356 + 'duplicates, but it is not a closed status. It should '. 357 + 'be a closed status.', 358 + $key)); 359 + } 360 + break; 361 + } 362 + 363 + $special_map[$special] = $key; 364 + } 365 + 366 + // NOTE: We're not explicitly validating that we have at least one open 367 + // and one closed status, because the DEFAULT and CLOSED specials imply 368 + // that to be true. If those change in the future, that might become a 369 + // reasonable thing to validate. 370 + 371 + $required = array( 372 + self::SPECIAL_DEFAULT, 373 + self::SPECIAL_CLOSED, 374 + self::SPECIAL_DUPLICATE, 375 + ); 376 + 377 + foreach ($required as $required_special) { 378 + if (!isset($special_map[$required_special])) { 379 + throw new Exception( 380 + pht( 381 + 'Configuration defines no task status with special attribute '. 382 + '"%s", but you must specify a status which fills this special '. 383 + 'role.', 384 + $required_special)); 385 + } 386 + } 252 387 } 253 388 254 389 }
+114
src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php
··· 1 + <?php 2 + 3 + final class ManiphestTaskStatusTestCase extends PhabricatorTestCase { 4 + 5 + public function testManiphestStatusConstants() { 6 + $map = array( 7 + 'y' => true, 8 + 'closed' => true, 9 + 'longlonglong' => true, 10 + 'duplicate2' => true, 11 + 12 + '' => false, 13 + 'longlonglonglong' => false, 14 + '.' => false, 15 + 'ABCD' => false, 16 + 'a b c ' => false, 17 + ); 18 + 19 + foreach ($map as $input => $expect) { 20 + $this->assertEqual( 21 + $expect, 22 + ManiphestTaskStatus::isValidStatusConstant($input), 23 + pht('Validate "%s"', $input)); 24 + } 25 + } 26 + 27 + public function testManiphestStatusConfigValidation() { 28 + $this->assertConfigValid( 29 + false, 30 + pht('Empty'), 31 + array()); 32 + 33 + // This is a minimal, valid configuration. 34 + 35 + $valid = array( 36 + 'open' => array( 37 + 'name' => 'Open', 38 + 'special' => 'default', 39 + ), 40 + 'closed' => array( 41 + 'name' => 'Closed', 42 + 'special' => 'closed', 43 + 'closed' => true, 44 + ), 45 + 'duplicate' => array( 46 + 'name' => 'Duplicate', 47 + 'special' => 'duplicate', 48 + 'closed' => true, 49 + ), 50 + ); 51 + $this->assertConfigValid(true, pht('Minimal Valid Config'), $valid); 52 + 53 + // We should raise on a bad key. 54 + $bad_key = $valid; 55 + $bad_key['!'] = array('name' => 'Exclaim'); 56 + $this->assertConfigValid(false, pht('Bad Key'), $bad_key); 57 + 58 + // We should raise on a value type. 59 + $bad_type = $valid; 60 + $bad_type['other'] = 'other'; 61 + $this->assertConfigValid(false, pht('Bad Value Type'), $bad_type); 62 + 63 + // We should raise on an unknown configuration key. 64 + $invalid_key = $valid; 65 + $invalid_key['open']['imaginary'] = 'unicorn'; 66 + $this->assertConfigValid(false, pht('Invalid Key'), $invalid_key); 67 + 68 + // We should raise on two statuses with the same special. 69 + $double_close = $valid; 70 + $double_close['finished'] = array( 71 + 'name' => 'Finished', 72 + 'special' => 'closed', 73 + 'closed' => true, 74 + ); 75 + $this->assertConfigValid(false, pht('Duplicate Special'), $double_close); 76 + 77 + // We should raise if any of the special statuses are missing. 78 + foreach ($valid as $key => $config) { 79 + $missing = $valid; 80 + unset($missing[$key]); 81 + $this->assertConfigValid(false, pht('Missing Special'), $missing); 82 + } 83 + 84 + // The "default" special should be an open status. 85 + $closed_default = $valid; 86 + $closed_default['open']['closed'] = true; 87 + $this->assertConfigValid(false, pht('Closed Default'), $closed_default); 88 + 89 + // The "closed" special should be a closed status. 90 + $open_closed = $valid; 91 + $open_closed['closed']['closed'] = false; 92 + $this->assertConfigValid(false, pht('Open Closed'), $open_closed); 93 + 94 + // The "duplicate" special should be a closed status. 95 + $open_duplicate = $valid; 96 + $open_duplicate['duplicate']['closed'] = false; 97 + $this->assertConfigValid(false, pht('Open Duplicate'), $open_duplicate); 98 + } 99 + 100 + private function assertConfigValid($expect, $name, array $config) { 101 + $caught = null; 102 + try { 103 + ManiphestTaskStatus::validateConfiguration($config); 104 + } catch (Exception $ex) { 105 + $caught = $ex; 106 + } 107 + 108 + $this->assertEqual( 109 + $expect, 110 + !($caught instanceof Exception), 111 + pht('Validation of "%s"', $name)); 112 + } 113 + 114 + }