mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 00:42:41 +01:00
Fix project hashtag bugs: duplicate tags, uppercase tags
Summary: Ref T8509. This fixes three issues: - Adding a slug like `UPPERCASE` would not give you a normalized slug. (Now: normalizes as `uppercase`.) - Adding a slug like `UPPERCASE` would allow you to give two different projects the different tags `UPPERCASE` and `uppercase` (and `UpPeRcAsE`, etc). (Now: second tag is rejected as a duplicate.) - Adding multiple identical or similar slugs would produce a duplicate key exception. (Now: ignores the duplicates.) Test Plan: - Added test coverage. - Made tests pass. - Hit these cases in the UI. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8509 Differential Revision: https://secure.phabricator.com/D14870
This commit is contained in:
parent
dc5397b2db
commit
92912a6072
3 changed files with 105 additions and 7 deletions
|
@ -249,6 +249,90 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
|
||||||
$milestone->getMemberPHIDs());
|
$milestone->getMemberPHIDs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDuplicateSlugs() {
|
||||||
|
// Creating a project with multiple duplicate slugs should succeed.
|
||||||
|
|
||||||
|
$user = $this->createUser();
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
$project = $this->createProject($user);
|
||||||
|
|
||||||
|
$input = 'duplicate';
|
||||||
|
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||||
|
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
||||||
|
->setNewValue(array($input, $input));
|
||||||
|
|
||||||
|
$this->applyTransactions($project, $user, $xactions);
|
||||||
|
|
||||||
|
$project = id(new PhabricatorProjectQuery())
|
||||||
|
->setViewer($user)
|
||||||
|
->withPHIDs(array($project->getPHID()))
|
||||||
|
->needSlugs(true)
|
||||||
|
->executeOne();
|
||||||
|
|
||||||
|
$slugs = $project->getSlugs();
|
||||||
|
$slugs = mpull($slugs, 'getSlug');
|
||||||
|
|
||||||
|
$this->assertTrue(in_array($input, $slugs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizeSlugs() {
|
||||||
|
// When a user creates a project with slug "XxX360n0sc0perXxX", normalize
|
||||||
|
// it before writing it.
|
||||||
|
|
||||||
|
$user = $this->createUser();
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
$project = $this->createProject($user);
|
||||||
|
|
||||||
|
$input = 'NoRmAlIzE';
|
||||||
|
$expect = 'normalize';
|
||||||
|
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||||
|
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
||||||
|
->setNewValue(array($input));
|
||||||
|
|
||||||
|
$this->applyTransactions($project, $user, $xactions);
|
||||||
|
|
||||||
|
$project = id(new PhabricatorProjectQuery())
|
||||||
|
->setViewer($user)
|
||||||
|
->withPHIDs(array($project->getPHID()))
|
||||||
|
->needSlugs(true)
|
||||||
|
->executeOne();
|
||||||
|
|
||||||
|
$slugs = $project->getSlugs();
|
||||||
|
$slugs = mpull($slugs, 'getSlug');
|
||||||
|
|
||||||
|
$this->assertTrue(in_array($expect, $slugs));
|
||||||
|
|
||||||
|
|
||||||
|
// If another user tries to add the same slug in denormalized form, it
|
||||||
|
// should be caught and fail, even though the database version of the slug
|
||||||
|
// is normalized.
|
||||||
|
|
||||||
|
$project2 = $this->createProject($user);
|
||||||
|
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(new PhabricatorProjectTransaction())
|
||||||
|
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
|
||||||
|
->setNewValue(array($input));
|
||||||
|
|
||||||
|
$caught = null;
|
||||||
|
try {
|
||||||
|
$this->applyTransactions($project2, $user, $xactions);
|
||||||
|
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
||||||
|
$caught = $ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertTrue((bool)$caught);
|
||||||
|
}
|
||||||
|
|
||||||
public function testParentProject() {
|
public function testParentProject() {
|
||||||
$user = $this->createUser();
|
$user = $this->createUser();
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
|
@ -68,7 +68,6 @@ final class PhabricatorProjectTransactionEditor
|
||||||
|
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case PhabricatorProjectTransaction::TYPE_NAME:
|
case PhabricatorProjectTransaction::TYPE_NAME:
|
||||||
case PhabricatorProjectTransaction::TYPE_SLUGS:
|
|
||||||
case PhabricatorProjectTransaction::TYPE_STATUS:
|
case PhabricatorProjectTransaction::TYPE_STATUS:
|
||||||
case PhabricatorProjectTransaction::TYPE_IMAGE:
|
case PhabricatorProjectTransaction::TYPE_IMAGE:
|
||||||
case PhabricatorProjectTransaction::TYPE_ICON:
|
case PhabricatorProjectTransaction::TYPE_ICON:
|
||||||
|
@ -76,6 +75,8 @@ final class PhabricatorProjectTransactionEditor
|
||||||
case PhabricatorProjectTransaction::TYPE_LOCKED:
|
case PhabricatorProjectTransaction::TYPE_LOCKED:
|
||||||
case PhabricatorProjectTransaction::TYPE_PARENT:
|
case PhabricatorProjectTransaction::TYPE_PARENT:
|
||||||
return $xaction->getNewValue();
|
return $xaction->getNewValue();
|
||||||
|
case PhabricatorProjectTransaction::TYPE_SLUGS:
|
||||||
|
return $this->normalizeSlugs($xaction->getNewValue());
|
||||||
case PhabricatorProjectTransaction::TYPE_MILESTONE:
|
case PhabricatorProjectTransaction::TYPE_MILESTONE:
|
||||||
$current = queryfx_one(
|
$current = queryfx_one(
|
||||||
$object->establishConnection('w'),
|
$object->establishConnection('w'),
|
||||||
|
@ -313,7 +314,9 @@ final class PhabricatorProjectTransactionEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
$slug_xaction = last($xactions);
|
$slug_xaction = last($xactions);
|
||||||
|
|
||||||
$new = $slug_xaction->getNewValue();
|
$new = $slug_xaction->getNewValue();
|
||||||
|
$new = $this->normalizeSlugs($new);
|
||||||
|
|
||||||
if ($new) {
|
if ($new) {
|
||||||
$slugs_used_already = id(new PhabricatorProjectSlug())
|
$slugs_used_already = id(new PhabricatorProjectSlug())
|
||||||
|
@ -332,7 +335,7 @@ final class PhabricatorProjectTransactionEditor
|
||||||
$type,
|
$type,
|
||||||
pht('Invalid'),
|
pht('Invalid'),
|
||||||
pht(
|
pht(
|
||||||
'Project hashtag %s is already the primary hashtag.',
|
'Project hashtag "%s" is already the primary hashtag.',
|
||||||
$object->getPrimarySlug()),
|
$object->getPrimarySlug()),
|
||||||
$slug_xaction);
|
$slug_xaction);
|
||||||
$errors[] = $error;
|
$errors[] = $error;
|
||||||
|
@ -344,8 +347,8 @@ final class PhabricatorProjectTransactionEditor
|
||||||
$type,
|
$type,
|
||||||
pht('Invalid'),
|
pht('Invalid'),
|
||||||
pht(
|
pht(
|
||||||
'%d project hashtag(s) are already used: %s.',
|
'%s project hashtag(s) are already used by other projects: %s.',
|
||||||
count($used_slug_strs),
|
phutil_count($used_slug_strs),
|
||||||
implode(', ', $used_slug_strs)),
|
implode(', ', $used_slug_strs)),
|
||||||
$slug_xaction);
|
$slug_xaction);
|
||||||
$errors[] = $error;
|
$errors[] = $error;
|
||||||
|
@ -640,4 +643,15 @@ final class PhabricatorProjectTransactionEditor
|
||||||
return parent::applyFinalEffects($object, $xactions);
|
return parent::applyFinalEffects($object, $xactions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function normalizeSlugs(array $slugs) {
|
||||||
|
foreach ($slugs as $key => $slug) {
|
||||||
|
$slugs[$key] = PhabricatorSlug::normalizeProjectSlug($slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
$slugs = array_unique($slugs);
|
||||||
|
$slugs = array_values($slugs);
|
||||||
|
|
||||||
|
return $slugs;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -766,9 +766,9 @@ final class PhabricatorUSEnglishTranslation
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
'%d project hashtag(s) are already used: %s.' => array(
|
'%s project hashtag(s) are already used by other projects: %s.' => array(
|
||||||
'Project hashtag %2$s is already used.',
|
'Project hashtag "%2$s" is already used by another project.',
|
||||||
'%d project hashtags are already used: %2$s.',
|
'Some project hashtags are already used by other projects: %2$s.',
|
||||||
),
|
),
|
||||||
|
|
||||||
'%s changed project hashtag(s), added %d: %s; removed %d: %s.' =>
|
'%s changed project hashtag(s), added %d: %s; removed %d: %s.' =>
|
||||||
|
|
Loading…
Reference in a new issue