diff --git a/.env.test b/.env.test index 959ef8ad..09379dc7 100644 --- a/.env.test +++ b/.env.test @@ -1,7 +1,7 @@ # define your env variables for the test env here KERNEL_CLASS='App\Kernel' APP_SECRET='$ecretf0rt3st' -SYMFONY_DEPRECATIONS_HELPER=999999 +SYMFONY_DEPRECATIONS_HELPER=weak PANTHER_APP_ENV=panther PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots @@ -9,4 +9,4 @@ APP_SECURITY_OAUTH_DISCORD_SERVER_ID= APP_SECURITY_API_ALLOWED_KEYS=test_key -APP_URL_TEAMSPEAK='teamspeak.example.com' +APP_URL_TEAMSPEAK='ts3server://ts.localhost.com?password=test' diff --git a/src/Form/Mod/Dto/ModFormDto.php b/src/Form/Mod/Dto/ModFormDto.php index abbb1f8f..c71f922a 100644 --- a/src/Form/Mod/Dto/ModFormDto.php +++ b/src/Form/Mod/Dto/ModFormDto.php @@ -13,9 +13,9 @@ use Ramsey\Uuid\UuidInterface; use Symfony\Component\Validator\Constraints as Assert; -#[UniqueSteamWorkshopMod(groups: [ModSourceEnum::STEAM_WORKSHOP->value])] +#[UniqueSteamWorkshopMod(groups: [ModSourceEnum::STEAM_WORKSHOP->value], errorPath: 'url')] #[SteamWorkshopArma3ModUrl(groups: [ModSourceEnum::STEAM_WORKSHOP->value], errorPath: 'url', nameErrorPath: 'name')] -#[UniqueDirectoryMod(groups: [ModSourceEnum::DIRECTORY->value])] +#[UniqueDirectoryMod(groups: [ModSourceEnum::DIRECTORY->value], errorPath: 'directory')] class ModFormDto extends AbstractFormDto { protected ?UuidInterface $id = null; diff --git a/src/Service/SteamApiClient/Helper/SteamHelper.php b/src/Service/SteamApiClient/Helper/SteamHelper.php index ecca0700..236d73a7 100644 --- a/src/Service/SteamApiClient/Helper/SteamHelper.php +++ b/src/Service/SteamApiClient/Helper/SteamHelper.php @@ -9,8 +9,8 @@ class SteamHelper { - public const ITEM_URL_REGEX = '~^https://steamcommunity\.com/(?:sharedfiles|workshop)/filedetails/\?id=(\d+)$~'; - public const APP_URL_REGEX = '~^https://store\.steampowered\.com/app/(\d+)~'; + private const ITEM_URL_REGEX = '~^https://steamcommunity\.com/(?:sharedfiles|workshop)/filedetails/\?id=(\d+)$~'; + private const APP_URL_REGEX = '~^https://store\.steampowered\.com/app/(\d+)~'; public static function profileIdToProfileUrl(int $profileId): string { diff --git a/src/Validator/Mod/UniqueDirectoryModValidator.php b/src/Validator/Mod/UniqueDirectoryModValidator.php index 15624d53..70266a76 100644 --- a/src/Validator/Mod/UniqueDirectoryModValidator.php +++ b/src/Validator/Mod/UniqueDirectoryModValidator.php @@ -23,8 +23,12 @@ public function validate(mixed $value, Constraint $constraint): void } $directory = $value->getDirectory(); + if ('' === $directory || null === $directory) { + return; + } + $id = $value->getId(); - if (!$directory || $this->isColumnValueUnique(DirectoryMod::class, ['directory' => $directory], $id)) { + if ($this->isColumnValueUnique(DirectoryMod::class, ['directory' => $directory], $id)) { return; } diff --git a/src/Validator/WindowsDirectoryName.php b/src/Validator/WindowsDirectoryName.php index c2ed62f7..3c34b395 100644 --- a/src/Validator/WindowsDirectoryName.php +++ b/src/Validator/WindowsDirectoryName.php @@ -13,7 +13,7 @@ class WindowsDirectoryName extends Regex public $message = 'Invalid directory name.'; /** @var string */ - public $pattern = '/^.{1,248}[^<>:"\/\|?*]$/ui'; + public $pattern = '/^.[^\<\>\:\"\/\|\?\*]{1,248}$/ui'; public function __construct( string $pattern = null, diff --git a/tests/_support/FunctionalTester.php b/tests/_support/FunctionalTester.php index 7e278414..0c85cc56 100644 --- a/tests/_support/FunctionalTester.php +++ b/tests/_support/FunctionalTester.php @@ -4,6 +4,8 @@ namespace App\Tests; +use App\Entity\User\User; +use App\Test\Traits\TimeTrait; use Codeception\Actor; use Codeception\Lib\Friend; @@ -25,8 +27,49 @@ class FunctionalTester extends Actor { use _generated\FunctionalTesterActions; + use TimeTrait; - /** - * Define custom actions here. - */ + public function amDiscordAuthenticatedAs(string $id, callable $preAuthCallback = null): User + { + /** @var User $user */ + $user = $this->grabEntityFromRepository(User::class, ['id' => $id]); + if ($preAuthCallback) { + $preAuthCallback($user); + + // Refresh user entity to avoid permission issues in subsequent requests + $this->haveInRepository($user); + } + $this->amLoggedInAs($user); + + return $user; + } + + public function seeResponseRedirectsTo(string $url): void + { + $this->seeResponseCodeIsRedirection(); + $this->seeHttpHeader('Location', $url); + } + + public function seeResponseRedirectsToDiscordAuth(): void + { + $this->seeResponseRedirectsTo('/security/connect/discord'); + } + + public function seeActionButton(string $tooltip, string $url = null): void + { + $selector = sprintf('a i[title="%s"]', $tooltip); + if ($url) { + $selector = sprintf('a[href="%s"] i[title="%s"]', $url, $tooltip); + } + $this->seeElement($selector); + } + + public function dontSeeActionButton(string $tooltip, string $url = null): void + { + $selector = sprintf('a i[title="%s"]', $tooltip); + if ($url) { + $selector = sprintf('a[href="%s"] i[title="%s"]', $url, $tooltip); + } + $this->dontSeeElement($selector); + } } diff --git a/tests/functional/Web/Mod/CreateDirectoryModCest.php b/tests/functional/Web/Mod/CreateDirectoryModCest.php new file mode 100644 index 00000000..6bd94422 --- /dev/null +++ b/tests/functional/Web/Mod/CreateDirectoryModCest.php @@ -0,0 +1,177 @@ +stopFollowingRedirects(); + $I->freezeTime('2020-01-01T00:00:00+00:00'); + } + + public function createDirectoryModAsUnauthenticatedUser(FunctionalTester $I): void + { + $I->amOnPage('/mod/create'); + $I->seeResponseRedirectsToDiscordAuth(); + } + + public function createDirectoryModAsUnauthorizedUser(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID); + + $I->amOnPage('/mod/create'); + $I->seeResponseCodeIs(Response::HTTP_FORBIDDEN); + } + + public function createDirectoryModAsAuthorizedUser(FunctionalTester $I): void + { + $user = $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + // Default form values + $I->seeOptionIsSelected('Mod source', 'Steam Workshop'); + $I->seeInField('Mod directory', ''); + $I->dontSee('Mod status'); // Not visible + $I->seeInField('Mod name', ''); + $I->seeInField('Mod description', ''); + + // Fill form + $I->selectOption('Mod source', 'Directory'); + $I->fillField('Mod directory', '@OCAP'); + $I->fillField('Mod name', 'OCAP'); + $I->fillField('Mod description', 'OCAP - AAR'); + $I->click('Create mod'); + + $I->seeResponseRedirectsTo('/mod/list'); + + /** @var DirectoryMod $mod */ + $mod = $I->grabEntityFromRepository(DirectoryMod::class, ['directory' => '@OCAP']); + $I->assertSame(null, $mod->getStatus()); + $I->assertSame('OCAP', $mod->getName()); + $I->assertSame('OCAP - AAR', $mod->getDescription()); + + $I->assertSame('2020-01-01T00:00:00+00:00', $mod->getCreatedAt()->format(DATE_ATOM)); + // $I->assertSame($user, $mod->getCreatedBy()); // FIXME + $I->assertSame(null, $mod->getLastUpdatedAt()); + $I->assertSame(null, $mod->getLastUpdatedBy()); + } + + public function createDirectoryModAsAuthorizedUserWithChangeModStatusPermission(FunctionalTester $I): void + { + $user = $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + $user->getPermissions()->modChangeStatus = true; + }); + + $I->amOnPage('/mod/create'); + + // Default form values + $I->seeOptionIsSelected('Mod source', 'Steam Workshop'); + $I->seeInField('Mod directory', ''); + $I->seeOptionIsSelected('Mod status', ''); + $I->seeInField('Mod name', ''); + $I->seeInField('Mod description', ''); + + // Fill form + $I->selectOption('Mod source', 'Directory'); + $I->fillField('Mod directory', '@OCAP'); + $I->selectOption('Mod status', 'Deprecated'); + $I->fillField('Mod name', 'OCAP'); + $I->fillField('Mod description', 'OCAP - AAR'); + $I->click('Create mod'); + + $I->seeResponseRedirectsTo('/mod/list'); + + /** @var DirectoryMod $mod */ + $mod = $I->grabEntityFromRepository(DirectoryMod::class, ['directory' => '@OCAP']); + $I->assertSame(ModStatusEnum::DEPRECATED, $mod->getStatus()); + $I->assertSame('OCAP', $mod->getName()); + $I->assertSame('OCAP - AAR', $mod->getDescription()); + + $I->assertSame('2020-01-01T00:00:00+00:00', $mod->getCreatedAt()->format(DATE_ATOM)); + // $I->assertSame($user, $mod->getCreatedBy()); // FIXME + $I->assertSame(null, $mod->getLastUpdatedAt()); + $I->assertSame(null, $mod->getLastUpdatedBy()); + } + + public function createDirectoryModAsAuthorizedUserWhenModAlreadyExists(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + $I->selectOption('Mod source', 'Directory'); + $I->fillField('Mod directory', ArmaScriptProfilerModFixture::DIRECTORY); + $I->fillField('Mod name', 'Arma Script Profiler'); + $I->click('Create mod'); + + $I->canSeeFormErrorMessage('directory', 'Mod associated with directory "@Arma Script Profiler" already exist.'); + } + + public function createDirectoryModAsAuthorizedUserWithInvalidDirectoryName(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + $I->selectOption('Mod source', 'Directory'); + $I->fillField('Mod directory', '@OC/AP'); + $I->fillField('Mod name', 'OCAP'); + $I->click('Create mod'); + + $I->canSeeFormErrorMessage('directory', 'Invalid directory name.'); + } + + public function createDirectoryModAsAuthorizedUserWithoutRequiredData(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + $I->selectOption('Mod source', 'Directory'); + $I->fillField('Mod directory', ''); + $I->fillField('Mod name', ''); + $I->click('Create mod'); + + $I->canSeeFormErrorMessage('directory', 'This value should not be blank.'); + $I->canSeeFormErrorMessage('name', 'This value should not be blank.'); + } + + public function createDirectoryModAsAuthorizedUserWithDataTooLong(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + $I->selectOption('Mod source', 'Directory'); + $I->fillField('Mod directory', '@OCAP'); + $I->fillField('Mod name', str_repeat('a', 256)); + $I->fillField('Mod description', str_repeat('a', 256)); + $I->click('Create mod'); + + $I->canSeeFormErrorMessage('name', 'This value is too long. It should have 255 characters or less.'); + $I->canSeeFormErrorMessage('description', 'This value is too long. It should have 255 characters or less.'); + } +} diff --git a/tests/functional/Web/Mod/CreateSteamWorkshopModCest.php b/tests/functional/Web/Mod/CreateSteamWorkshopModCest.php new file mode 100644 index 00000000..af05cae6 --- /dev/null +++ b/tests/functional/Web/Mod/CreateSteamWorkshopModCest.php @@ -0,0 +1,248 @@ +stopFollowingRedirects(); + $I->freezeTime('2020-01-01T00:00:00+00:00'); + } + + public function createSteamWorkshopModAsUnauthenticatedUser(FunctionalTester $I): void + { + $I->amOnPage('/mod/create'); + $I->seeResponseRedirectsToDiscordAuth(); + } + + public function createSteamWorkshopModAsUnauthorizedUser(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID); + + $I->amOnPage('/mod/create'); + $I->seeResponseCodeIs(Response::HTTP_FORBIDDEN); + } + + public function createSteamWorkshopModAsAuthorizedUser(FunctionalTester $I): void + { + $user = $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + // Default form values + $I->seeOptionIsSelected('Mod source', 'Steam Workshop'); + $I->seeInField('Steam Workshop URL', ''); + $I->seeOptionIsSelected('Mod type', 'Required mod'); + $I->dontSee('Mod status'); // Not visible + $I->seeInField('Mod name', ''); + $I->seeInField('Mod description', ''); + + // Fill form + $I->selectOption('Mod source', 'Steam Workshop'); + $I->fillField('Steam Workshop URL', 'https://steamcommunity.com/sharedfiles/filedetails/?id=1934142795'); + $I->selectOption('Mod type', 'Optional mod'); + $I->fillField('Mod name', 'AF Mods'); + $I->fillField('Mod description', 'Custom Arma 3 mods'); + $I->click('Create mod'); + + $I->seeResponseRedirectsTo('/mod/list'); + + /** @var SteamWorkshopMod $mod */ + $mod = $I->grabEntityFromRepository(SteamWorkshopMod::class, ['itemId' => 1934142795]); + $I->assertSame(ModTypeEnum::OPTIONAL, $mod->getType()); + $I->assertSame(null, $mod->getStatus()); + $I->assertSame('AF Mods', $mod->getName()); + $I->assertSame('Custom Arma 3 mods', $mod->getDescription()); + + $I->assertSame('2020-01-01T00:00:00+00:00', $mod->getCreatedAt()->format(DATE_ATOM)); + // $I->assertSame($user, $mod->getCreatedBy()); // FIXME + $I->assertSame(null, $mod->getLastUpdatedAt()); + $I->assertSame(null, $mod->getLastUpdatedBy()); + } + + public function createSteamWorkshopModAsAuthorizedUserWithNameProvidedBySteamWorkshop(FunctionalTester $I): void + { + $user = $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + // Default form values + $I->seeOptionIsSelected('Mod source', 'Steam Workshop'); + $I->seeInField('Steam Workshop URL', ''); + $I->seeOptionIsSelected('Mod type', 'Required mod'); + $I->dontSee('Mod status'); // Not visible + $I->seeInField('Mod name', ''); + $I->seeInField('Mod description', ''); + + // Fill form + $I->selectOption('Mod source', 'Steam Workshop'); + $I->fillField('Steam Workshop URL', 'https://steamcommunity.com/sharedfiles/filedetails/?id=1934142795'); + $I->selectOption('Mod type', 'Required mod'); + $I->fillField('Mod name', ''); + $I->fillField('Mod description', ''); + $I->click('Create mod'); + + $I->seeResponseRedirectsTo('/mod/list'); + + /** @var SteamWorkshopMod $mod */ + $mod = $I->grabEntityFromRepository(SteamWorkshopMod::class, ['itemId' => 1934142795]); + $I->assertSame(ModTypeEnum::REQUIRED, $mod->getType()); + $I->assertSame(null, $mod->getStatus()); + $I->assertSame('ArmaForces - Mods', $mod->getName()); // From Steam Workshop + $I->assertSame(null, $mod->getDescription()); + + $I->assertSame('2020-01-01T00:00:00+00:00', $mod->getCreatedAt()->format(DATE_ATOM)); + // $I->assertSame($user, $mod->getCreatedBy()); // FIXME + $I->assertSame(null, $mod->getLastUpdatedAt()); + $I->assertSame(null, $mod->getLastUpdatedBy()); + } + + public function createSteamWorkshopModAsAuthorizedUserWithChangeModStatusPermission(FunctionalTester $I): void + { + $user = $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + $user->getPermissions()->modChangeStatus = true; + }); + + $I->amOnPage('/mod/create'); + + // Default form values + $I->seeOptionIsSelected('Mod source', 'Steam Workshop'); + $I->seeInField('Steam Workshop URL', ''); + $I->seeOptionIsSelected('Mod type', 'Required mod'); + $I->seeOptionIsSelected('Mod status', ''); + $I->seeInField('Mod name', ''); + $I->seeInField('Mod description', ''); + + // Fill form + $I->selectOption('Mod source', 'Steam Workshop'); + $I->fillField('Steam Workshop URL', 'https://steamcommunity.com/sharedfiles/filedetails/?id=1934142795'); + $I->selectOption('Mod type', 'Required mod'); + $I->selectOption('Mod status', 'Deprecated'); + $I->fillField('Mod name', 'AF Mods'); + $I->fillField('Mod description', 'Custom Arma 3 mods'); + $I->click('Create mod'); + + $I->seeResponseRedirectsTo('/mod/list'); + + /** @var SteamWorkshopMod $mod */ + $mod = $I->grabEntityFromRepository(SteamWorkshopMod::class, ['itemId' => 1934142795]); + $I->assertSame(ModTypeEnum::REQUIRED, $mod->getType()); + $I->assertSame(ModStatusEnum::DEPRECATED, $mod->getStatus()); + $I->assertSame('AF Mods', $mod->getName()); + $I->assertSame('Custom Arma 3 mods', $mod->getDescription()); + + $I->assertSame('2020-01-01T00:00:00+00:00', $mod->getCreatedAt()->format(DATE_ATOM)); + // $I->assertSame($user, $mod->getCreatedBy()); // FIXME + $I->assertSame(null, $mod->getLastUpdatedAt()); + $I->assertSame(null, $mod->getLastUpdatedBy()); + } + + public function createSteamWorkshopModAsAuthorizedUserWhenModAlreadyExists(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + $url = SteamHelper::itemIdToItemUrl(ArmaForcesMedicalModFixture::ITEM_ID); + $I->fillField('Steam Workshop URL', $url); + $I->click('Create mod'); + + $message = sprintf('Mod associated with url "%s" already exist.', $url); + $I->canSeeFormErrorMessage('url', $message); + } + + public function createSteamWorkshopModAsAuthorizedUserWithInvalidModUrl(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + $I->fillField('Steam Workshop URL', 'https://example.com'); + $I->click('Create mod'); + + $I->canSeeFormErrorMessage('url', 'Invalid Steam Workshop mod url.'); + } + + public function createSteamWorkshopModAsAuthorizedUserWhenModDoesNotExist(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + $url = SteamHelper::itemIdToItemUrl(9999999); + $I->fillField('Steam Workshop URL', $url); + $I->click('Create mod'); + + $I->canSeeFormErrorMessage('url', 'Mod not found.'); + } + + public function createSteamWorkshopModAsAuthorizedUserWhenUrlIsNotAnArma3Mod(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + $url = SteamHelper::itemIdToItemUrl(455312245); + $I->fillField('Steam Workshop URL', $url); + $I->click('Create mod'); + + $I->canSeeFormErrorMessage('url', 'Url is not an Arma 3 mod.'); + } + + public function createSteamWorkshopModAsAuthorizedUserWithoutRequiredData(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + $I->fillField('Steam Workshop URL', ''); + $I->click('Create mod'); + + $I->canSeeFormErrorMessage('url', 'This value should not be blank.'); + } + + public function createSteamWorkshopModAsAuthorizedUserWithDataTooLong(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/create'); + + $I->fillField('Steam Workshop URL', 'https://steamcommunity.com/sharedfiles/filedetails/?id=1934142795'); + $I->fillField('Mod name', str_repeat('a', 256)); + $I->fillField('Mod description', str_repeat('a', 256)); + $I->click('Create mod'); + + $I->canSeeFormErrorMessage('name', 'This value is too long. It should have 255 characters or less.'); + $I->canSeeFormErrorMessage('description', 'This value is too long. It should have 255 characters or less.'); + } +} diff --git a/tests/functional/Web/Mod/DeleteModCest.php b/tests/functional/Web/Mod/DeleteModCest.php new file mode 100644 index 00000000..cea784d2 --- /dev/null +++ b/tests/functional/Web/Mod/DeleteModCest.php @@ -0,0 +1,53 @@ +stopFollowingRedirects(); + } + + public function deleteModAsUnauthenticatedUser(FunctionalTester $I): void + { + $id = ArmaForcesMedicalModFixture::ID; + $I->amOnPage(sprintf('/mod/%s/delete', $id)); + $I->seeResponseRedirectsToDiscordAuth(); + + $I->seeInRepository(AbstractMod::class, ['id' => $id]); + } + + public function deleteModAsUnauthorizedUser(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID); + + $id = ArmaForcesMedicalModFixture::ID; + $I->amOnPage(sprintf('/mod/%s/delete', $id)); + $I->seeResponseCodeIs(Response::HTTP_FORBIDDEN); + + $I->seeInRepository(AbstractMod::class, ['id' => $id]); + } + + public function deleteModAsAuthorizedUser(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modDelete = true; + }); + + $id = ArmaForcesMedicalModFixture::ID; + $I->amOnPage(sprintf('/mod/%s/delete', $id)); + $I->seeResponseRedirectsTo('/mod/list'); + + $I->dontSeeInRepository(AbstractMod::class, ['id' => $id]); + } +} diff --git a/tests/functional/Web/Mod/ListModsCest.php b/tests/functional/Web/Mod/ListModsCest.php new file mode 100644 index 00000000..b903add7 --- /dev/null +++ b/tests/functional/Web/Mod/ListModsCest.php @@ -0,0 +1,90 @@ +stopFollowingRedirects(); + } + + public function listModsAsUnauthenticatedUser(FunctionalTester $I): void + { + $I->amOnPage('/mod/list'); + $I->seeResponseRedirectsToDiscordAuth(); + } + + public function listModsAsUnauthorizedUser(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID); + + $I->amOnPage('/mod/list'); + $I->seeResponseCodeIs(Response::HTTP_FORBIDDEN); + } + + public function listModsAsAuthorizedUser(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modList = true; + }); + + $I->amOnPage('/mod/list'); + $I->seeResponseCodeIs(Response::HTTP_OK); + + $I->dontSeeActionButton('Create mod'); + $I->dontSeeActionButton('Edit mod'); + } + + public function listModsAsAuthorizedUserWithCreateModPermission(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modList = true; + $user->getPermissions()->modCreate = true; + }); + + $I->amOnPage('/mod/list'); + $I->seeResponseCodeIs(Response::HTTP_OK); + + $I->seeLink('Create mod'); + $I->dontSeeActionButton('Edit mod'); + $I->dontSeeActionButton('Delete mod'); + } + + public function listModsAsAuthorizedUserWithUpdateModPermission(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modList = true; + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage('/mod/list'); + $I->seeResponseCodeIs(Response::HTTP_OK); + + $I->dontSeeLink('Create mod'); + $I->seeActionButton('Edit mod'); + $I->dontSeeActionButton('Delete mod'); + } + + public function listModsAsAuthorizedUserWithDeleteModPermission(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modList = true; + $user->getPermissions()->modDelete = true; + }); + + $I->amOnPage('/mod/list'); + $I->seeResponseCodeIs(Response::HTTP_OK); + + $I->dontSeeLink('Create mod'); + $I->dontSeeActionButton('Edit mod'); + $I->seeActionButton('Delete mod'); + } +} diff --git a/tests/functional/Web/Mod/UpdateDirectoryModCest.php b/tests/functional/Web/Mod/UpdateDirectoryModCest.php new file mode 100644 index 00000000..974b9151 --- /dev/null +++ b/tests/functional/Web/Mod/UpdateDirectoryModCest.php @@ -0,0 +1,176 @@ +stopFollowingRedirects(); + $I->freezeTime('2021-01-01T00:00:00+00:00'); + } + + public function updateDirectoryModAsUnauthenticatedUser(FunctionalTester $I): void + { + $I->amOnPage(sprintf('/mod/%s/update', ArmaScriptProfilerModFixture::ID)); + $I->seeResponseRedirectsToDiscordAuth(); + } + + public function updateDirectoryModAsUnauthorizedUser(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaScriptProfilerModFixture::ID)); + $I->seeResponseCodeIs(Response::HTTP_FORBIDDEN); + } + + public function updateDirectoryModAsAuthorizedUser(FunctionalTester $I): void + { + $user = $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaScriptProfilerModFixture::ID)); + + // Default form values + $I->seeOptionIsSelected('Mod source', 'Directory'); + $I->seeInField('Mod directory', '@Arma Script Profiler'); + $I->dontSee('Mod status'); // Not visible + $I->seeInField('Mod name', 'Arma Script Profiler'); + $I->seeInField('Mod description', ''); + + // Fill form + $I->fillField('Mod directory', '@OCAP'); + $I->fillField('Mod name', 'OCAP'); + $I->fillField('Mod description', 'OCAP - AAR'); + $I->click('Apply'); + + $I->seeResponseRedirectsTo('/mod/list'); + + /** @var DirectoryMod $mod */ + $mod = $I->grabEntityFromRepository(DirectoryMod::class, ['directory' => '@OCAP']); + $I->assertSame(null, $mod->getStatus()); + $I->assertSame('OCAP', $mod->getName()); + $I->assertSame('OCAP - AAR', $mod->getDescription()); + + $I->assertSame('2020-01-01T00:00:00+00:00', $mod->getCreatedAt()->format(DATE_ATOM)); + $I->assertSame(null, $mod->getCreatedBy()); + // $I->assertSame('2021-01-01T00:00:00+00:00', $mod->getLastUpdatedAt()->format(DATE_ATOM)); // FIXME + // $I->assertSame($user, $mod->getLastUpdatedBy()); // FIXME + } + + public function updateDirectoryModAsAuthorizedUserWithChangeModStatusPermission(FunctionalTester $I): void + { + $user = $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + $user->getPermissions()->modChangeStatus = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaScriptProfilerModFixture::ID)); + + // Default form values + $I->seeOptionIsSelected('Mod source', 'Directory'); + $I->seeInField('Mod directory', '@Arma Script Profiler'); + $I->seeOptionIsSelected('Mod status', ''); + $I->seeInField('Mod name', 'Arma Script Profiler'); + $I->seeInField('Mod description', ''); + + // Fill form + $I->fillField('Mod directory', '@OCAP'); + $I->selectOption('Mod status', 'Deprecated'); + $I->fillField('Mod name', 'OCAP'); + $I->fillField('Mod description', 'OCAP - AAR'); + $I->click('Apply'); + + $I->seeResponseRedirectsTo('/mod/list'); + + /** @var DirectoryMod $mod */ + $mod = $I->grabEntityFromRepository(DirectoryMod::class, ['directory' => '@OCAP']); + $I->assertSame(ModStatusEnum::DEPRECATED, $mod->getStatus()); + $I->assertSame('OCAP', $mod->getName()); + $I->assertSame('OCAP - AAR', $mod->getDescription()); + + $I->assertSame('2020-01-01T00:00:00+00:00', $mod->getCreatedAt()->format(DATE_ATOM)); + $I->assertSame(null, $mod->getCreatedBy()); + // $I->assertSame('2021-01-01T00:00:00+00:00', $mod->getLastUpdatedAt()->format(DATE_ATOM)); // FIXME + // $I->assertSame($user, $mod->getLastUpdatedBy()); // FIXME + } + + public function updateDirectoryModAsAuthorizedUserWhenModAlreadyExists(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaScriptProfilerModFixture::ID)); + + $I->selectOption('Mod source', 'Directory'); + $I->fillField('Mod directory', R3ModFixture::DIRECTORY); + $I->fillField('Mod name', 'R3'); + $I->click('Apply'); + + $I->canSeeFormErrorMessage('directory', 'Mod associated with directory "@R3" already exist.'); + } + + public function updateDirectoryModAsAuthorizedUserWithInvalidDirectoryName(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaScriptProfilerModFixture::ID)); + + $I->selectOption('Mod source', 'Directory'); + $I->fillField('Mod directory', '@OC/AP'); + $I->fillField('Mod name', 'OCAP'); + $I->click('Apply'); + + $I->canSeeFormErrorMessage('directory', 'Invalid directory name.'); + } + + public function updateDirectoryModAsAuthorizedUserWithoutRequiredData(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaScriptProfilerModFixture::ID)); + + $I->selectOption('Mod source', 'Directory'); + $I->fillField('Mod directory', ''); + $I->fillField('Mod name', ''); + $I->click('Apply'); + + $I->canSeeFormErrorMessage('directory', 'This value should not be blank.'); + $I->canSeeFormErrorMessage('name', 'This value should not be blank.'); + } + + public function updateDirectoryModAsAuthorizedUserWithDataTooLong(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaScriptProfilerModFixture::ID)); + + $I->selectOption('Mod source', 'Directory'); + $I->fillField('Mod directory', '@OCAP'); + $I->fillField('Mod name', str_repeat('a', 256)); + $I->fillField('Mod description', str_repeat('a', 256)); + $I->click('Apply'); + + $I->canSeeFormErrorMessage('name', 'This value is too long. It should have 255 characters or less.'); + $I->canSeeFormErrorMessage('description', 'This value is too long. It should have 255 characters or less.'); + } +} diff --git a/tests/functional/Web/Mod/UpdateSteamWorkshopModCest.php b/tests/functional/Web/Mod/UpdateSteamWorkshopModCest.php new file mode 100644 index 00000000..e192299c --- /dev/null +++ b/tests/functional/Web/Mod/UpdateSteamWorkshopModCest.php @@ -0,0 +1,246 @@ +stopFollowingRedirects(); + $I->freezeTime('2021-01-01T00:00:00+00:00'); + } + + public function updateSteamWorkshopModAsUnauthenticatedUser(FunctionalTester $I): void + { + $I->amOnPage(sprintf('/mod/%s/update', ArmaForcesMedicalModFixture::ID)); + $I->seeResponseRedirectsToDiscordAuth(); + } + + public function updateSteamWorkshopModAsUnauthorizedUser(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaForcesMedicalModFixture::ID)); + $I->seeResponseCodeIs(Response::HTTP_FORBIDDEN); + } + + public function updateSteamWorkshopModAsAuthorizedUser(FunctionalTester $I): void + { + $user = $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaForcesMedicalModFixture::ID)); + + // Default form values + $I->seeOptionIsSelected('Mod source', 'Steam Workshop'); + $I->seeInField('Steam Workshop URL', 'https://steamcommunity.com/sharedfiles/filedetails/?id=1981535406'); + $I->seeOptionIsSelected('Mod type', 'Required mod'); + $I->dontSee('Mod status'); // Not visible + $I->seeInField('Mod name', 'ArmaForces - Medical'); + $I->seeInField('Mod description', ''); + + // Fill form + $I->fillField('Steam Workshop URL', 'https://steamcommunity.com/sharedfiles/filedetails/?id=1934142795'); + $I->selectOption('Mod type', 'Optional mod'); + $I->fillField('Mod name', 'AF Mods'); + $I->fillField('Mod description', 'Custom Arma 3 mods'); + $I->click('Apply'); + + $I->seeResponseRedirectsTo('/mod/list'); + + /** @var SteamWorkshopMod $mod */ + $mod = $I->grabEntityFromRepository(SteamWorkshopMod::class, ['itemId' => 1934142795]); + $I->assertSame(ModTypeEnum::OPTIONAL, $mod->getType()); + $I->assertSame(null, $mod->getStatus()); + $I->assertSame('AF Mods', $mod->getName()); + $I->assertSame('Custom Arma 3 mods', $mod->getDescription()); + + $I->assertSame('2020-01-01T00:00:00+00:00', $mod->getCreatedAt()->format(DATE_ATOM)); + $I->assertSame(null, $mod->getCreatedBy()); + // $I->assertSame('2021-01-01T00:00:00+00:00', $mod->getLastUpdatedAt()->format(DATE_ATOM)); // FIXME + // $I->assertSame($user, $mod->getLastUpdatedBy()); // FIXME + } + + public function updateSteamWorkshopModAsAuthorizedUserWithNameProvidedBySteamWorkshop(FunctionalTester $I): void + { + $user = $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaForcesMedicalModFixture::ID)); + + // Default form values + $I->seeOptionIsSelected('Mod source', 'Steam Workshop'); + $I->seeInField('Steam Workshop URL', 'https://steamcommunity.com/sharedfiles/filedetails/?id=1981535406'); + $I->seeOptionIsSelected('Mod type', 'Required mod'); + $I->dontSee('Mod status'); // Not visible + $I->seeInField('Mod name', 'ArmaForces - Medical'); + $I->seeInField('Mod description', ''); + + // Fill form + $I->fillField('Steam Workshop URL', 'https://steamcommunity.com/sharedfiles/filedetails/?id=1934142795'); + $I->selectOption('Mod type', 'Optional mod'); + $I->fillField('Mod name', ''); + $I->fillField('Mod description', ''); + $I->click('Apply'); + + $I->seeResponseRedirectsTo('/mod/list'); + + /** @var SteamWorkshopMod $mod */ + $mod = $I->grabEntityFromRepository(SteamWorkshopMod::class, ['itemId' => 1934142795]); + $I->assertSame(ModTypeEnum::OPTIONAL, $mod->getType()); + $I->assertSame(null, $mod->getStatus()); + $I->assertSame('ArmaForces - Mods', $mod->getName()); // From Steam Workshop + $I->assertSame(null, $mod->getDescription()); + + $I->assertSame('2020-01-01T00:00:00+00:00', $mod->getCreatedAt()->format(DATE_ATOM)); + $I->assertSame(null, $mod->getCreatedBy()); + // $I->assertSame('2021-01-01T00:00:00+00:00', $mod->getLastUpdatedAt()->format(DATE_ATOM)); // FIXME + // $I->assertSame($user, $mod->getLastUpdatedBy()); // FIXME + } + + public function updateSteamWorkshopModAsAuthorizedUserWithChangeModStatusPermission(FunctionalTester $I): void + { + $user = $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + $user->getPermissions()->modChangeStatus = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaForcesMedicalModFixture::ID)); + + // Default form values + $I->seeOptionIsSelected('Mod source', 'Steam Workshop'); + $I->seeInField('Steam Workshop URL', 'https://steamcommunity.com/sharedfiles/filedetails/?id=1981535406'); + $I->seeOptionIsSelected('Mod type', 'Required mod'); + $I->seeOptionIsSelected('Mod status', ''); + $I->seeInField('Mod name', 'ArmaForces - Medical'); + $I->seeInField('Mod description', ''); + + // Fill form + $I->fillField('Steam Workshop URL', 'https://steamcommunity.com/sharedfiles/filedetails/?id=1934142795'); + $I->selectOption('Mod type', 'Optional mod'); + $I->selectOption('Mod status', 'Deprecated'); + $I->fillField('Mod name', 'AF Mods'); + $I->fillField('Mod description', 'Custom Arma 3 mods'); + $I->click('Apply'); + + $I->seeResponseRedirectsTo('/mod/list'); + + /** @var SteamWorkshopMod $mod */ + $mod = $I->grabEntityFromRepository(SteamWorkshopMod::class, ['itemId' => 1934142795]); + $I->assertSame(ModTypeEnum::OPTIONAL, $mod->getType()); + $I->assertSame(ModStatusEnum::DEPRECATED, $mod->getStatus()); + $I->assertSame('AF Mods', $mod->getName()); + $I->assertSame('Custom Arma 3 mods', $mod->getDescription()); + + $I->assertSame('2020-01-01T00:00:00+00:00', $mod->getCreatedAt()->format(DATE_ATOM)); + $I->assertSame(null, $mod->getCreatedBy()); + // $I->assertSame('2021-01-01T00:00:00+00:00', $mod->getLastUpdatedAt()->format(DATE_ATOM)); // FIXME + // $I->assertSame($user, $mod->getLastUpdatedBy()); // FIXME + } + + public function updateSteamWorkshopModAsAuthorizedUserWhenModDoesNotExist(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaForcesMedicalModFixture::ID)); + + $url = SteamHelper::itemIdToItemUrl(9999999); + $I->fillField('Steam Workshop URL', $url); + $I->click('Apply'); + + $I->canSeeFormErrorMessage('url', 'Mod not found.'); + } + + public function updateSteamWorkshopModAsAuthorizedUserWithInvalidModUrl(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaForcesMedicalModFixture::ID)); + + $I->fillField('Steam Workshop URL', 'https://example.com'); + $I->click('Apply'); + + $I->canSeeFormErrorMessage('url', 'Invalid Steam Workshop mod url.'); + } + + public function updateSteamWorkshopModAsAuthorizedUserWhenModAlreadyExists(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', LegacyArmaForcesModsModFixture::ID)); + + $url = SteamHelper::itemIdToItemUrl(ArmaForcesMedicalModFixture::ITEM_ID); + $I->fillField('Steam Workshop URL', $url); + $I->click('Apply'); + + $message = sprintf('Mod associated with url "%s" already exist.', $url); + $I->canSeeFormErrorMessage('url', $message); + } + + public function updateSteamWorkshopModAsAuthorizedUserWhenUrlIsNotAnArma3Mod(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaForcesMedicalModFixture::ID)); + + $url = SteamHelper::itemIdToItemUrl(455312245); + $I->fillField('Steam Workshop URL', $url); + $I->click('Apply'); + + $I->canSeeFormErrorMessage('url', 'Url is not an Arma 3 mod.'); + } + + public function updateSteamWorkshopModAsAuthorizedUserWithoutRequiredData(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaForcesMedicalModFixture::ID)); + + $I->fillField('Steam Workshop URL', ''); + $I->click('Apply'); + + $I->canSeeFormErrorMessage('url', 'This value should not be blank.'); + } + + public function updateSteamWorkshopModAsAuthorizedUserWithDataTooLong(FunctionalTester $I): void + { + $I->amDiscordAuthenticatedAs(RegularUserFixture::ID, function (User $user): void { + $user->getPermissions()->modUpdate = true; + }); + + $I->amOnPage(sprintf('/mod/%s/update', ArmaForcesMedicalModFixture::ID)); + + $I->fillField('Steam Workshop URL', 'https://steamcommunity.com/sharedfiles/filedetails/?id=1934142795'); + $I->fillField('Mod name', str_repeat('a', 256)); + $I->fillField('Mod description', str_repeat('a', 256)); + $I->click('Apply'); + + $I->canSeeFormErrorMessage('name', 'This value is too long. It should have 255 characters or less.'); + $I->canSeeFormErrorMessage('description', 'This value is too long. It should have 255 characters or less.'); + } +}