Skip to content

Commit

Permalink
Add public mod list tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jskowronski39 committed Jan 7, 2024
1 parent 52dd192 commit 07161c4
Show file tree
Hide file tree
Showing 17 changed files with 331 additions and 125 deletions.
4 changes: 1 addition & 3 deletions src/Api/Controller/GetModListByNameAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ public function __construct(

public function __invoke(string $name): ?ModList
{
$modList = $this->modListRepository->findOneBy([
'name' => $name,
]);
$modList = $this->modListRepository->findOneByName($name);

if (!$modList) {
throw new NotFoundHttpException('Not Found');
Expand Down
4 changes: 3 additions & 1 deletion src/Controller/ModListPublic/DownloadAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

use function Symfony\Component\Clock\now;

class DownloadAction extends AbstractController
{
public function __construct(
Expand All @@ -24,7 +26,7 @@ public function __construct(
#[IsGranted(PermissionsEnum::MOD_LIST_DOWNLOAD->value, 'modList')]
public function __invoke(ModList $modList, string $optionalModsJson = null): Response
{
$name = sprintf('ArmaForces %s %s', $modList->getName(), (new \DateTimeImmutable())->format('Y_m_d H_i'));
$name = sprintf('ArmaForces %s %s', $modList->getName(), now()->format('Y_m_d H_i'));
$mods = $this->modRepository->findIncludedSteamWorkshopMods($modList);
$optionalMods = json_decode($optionalModsJson ?? '', true) ?: [];

Expand Down
2 changes: 1 addition & 1 deletion src/Controller/ModListPublic/SelectAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function __invoke(): Response

$nextMissionModList = null;
if ($nextMission) {
$nextMissionModList = $this->modListRepository->findOneBy(['name' => $nextMission->getModlist()]);
$nextMissionModList = $this->modListRepository->findOneByName($nextMission->getModlist());
}

return $this->render('mod_list_public/select.html.twig', [
Expand Down
5 changes: 5 additions & 0 deletions src/Repository/ModList/ModListRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@ public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ModList::class);
}

public function findOneByName(string $name): ?ModList
{
return $this->findOneBy(['name' => $name]);
}
}
87 changes: 6 additions & 81 deletions tests/_support/FunctionalTester.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace App\Tests;

use App\Entity\User\User;
use App\Test\Traits\TimeTrait;
use App\Tests\Traits\DataTableAssertsTrait;
use App\Tests\Traits\ResponseAssertTrait;
use App\Tests\Traits\SecurityAssertsTrait;
use Codeception\Actor;
use Codeception\Lib\Friend;

Expand All @@ -27,85 +29,8 @@
class FunctionalTester extends Actor
{
use _generated\FunctionalTesterActions;
use DataTableAssertsTrait;
use ResponseAssertTrait;
use SecurityAssertsTrait;
use TimeTrait;

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 seeResponseRedirectsToLogInAction(): void
{
$this->seeResponseRedirectsTo('/security/connect/discord');
}

public function seeResponseRedirectsToDiscordOauth(): void
{
$this->seeResponseCodeIsRedirection();
$redirect = $this->grabHttpHeader('Location');
$this->assertTrue(str_starts_with($redirect, 'https://discord.com/oauth2/authorize'));
}

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);
}

public function checkTableRowCheckbox(string $value): void
{
$this->checkOption(sprintf('[value="%s"]', $value));
}

public function uncheckTableRowCheckbox(string $value): void
{
$this->uncheckOption(sprintf('[value="%s"]', $value));
}

public function seeTableRowCheckboxesAreChecked(array $values = []): void
{
$valueSelector = array_map(fn (string $value) => sprintf('[value="%s"]', $value), $values);
$checkboxSelector = implode(', ', $valueSelector);

$this->seeCheckboxIsChecked($checkboxSelector);
}

public function seeTableRowCheckboxesAreUnchecked(string $idPrefix, array $valuesToExclude = []): void
{
$checkboxSelector = sprintf('[id^="%s"]', $idPrefix);
if ($valuesToExclude) {
$valueSelector = array_map(fn (string $value) => sprintf(':not([value^="%s"])', $value), $valuesToExclude);
$checkboxSelector .= implode('', $valueSelector);
}

$this->dontSeeCheckboxIsChecked($checkboxSelector);
}
}
12 changes: 0 additions & 12 deletions tests/_support/Helper/Functional.php

This file was deleted.

12 changes: 0 additions & 12 deletions tests/_support/Helper/Integration.php

This file was deleted.

12 changes: 0 additions & 12 deletions tests/_support/Helper/Unit.php

This file was deleted.

55 changes: 55 additions & 0 deletions tests/_support/Traits/DataTableAssertsTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace App\Tests\Traits;

trait DataTableAssertsTrait
{
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);
}

public function checkTableRowCheckbox(string $value): void
{
$this->checkOption(sprintf('[value="%s"]', $value));
}

public function uncheckTableRowCheckbox(string $value): void
{
$this->uncheckOption(sprintf('[value="%s"]', $value));
}

public function seeTableRowCheckboxesAreChecked(array $values = []): void
{
$valueSelector = array_map(fn (string $value) => sprintf('[value="%s"]', $value), $values);
$checkboxSelector = implode(', ', $valueSelector);

$this->seeCheckboxIsChecked($checkboxSelector);
}

public function seeTableRowCheckboxesAreUnchecked(string $idPrefix, array $valuesToExclude = []): void
{
$checkboxSelector = sprintf('[id^="%s"]', $idPrefix);
if ($valuesToExclude) {
$valueSelector = array_map(fn (string $value) => sprintf(':not([value^="%s"])', $value), $valuesToExclude);
$checkboxSelector .= implode('', $valueSelector);
}

$this->dontSeeCheckboxIsChecked($checkboxSelector);
}
}
73 changes: 73 additions & 0 deletions tests/_support/Traits/ResponseAssertTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace App\Tests\Traits;

use App\Entity\Dlc\Dlc;
use App\Entity\Mod\SteamWorkshopMod;
use Symfony\Component\DomCrawler\Crawler;

trait ResponseAssertTrait
{
public function seeResponseRedirectsTo(string $url): void
{
$this->seeResponseCodeIsRedirection();
$this->seeHttpHeader('Location', $url);
}

public function seeResponseRedirectsToLogInAction(): void
{
$this->seeResponseRedirectsTo('/security/connect/discord');
}

public function seeResponseRedirectsToDiscordOauth(): void
{
$this->seeResponseCodeIsRedirection();
$redirect = $this->grabHttpHeader('Location');
$this->assertTrue(str_starts_with($redirect, 'https://discord.com/oauth2/authorize'));
}

public function seeResponseContainsModListPresetWithMods(
string $fileName,
array $expectedDlcs,
array $expectedMods,
): void {
$extractSteamWorkshopItems = function (Crawler $crawler, string $containerName) {
$containerSelector = sprintf('[data-type="%s"]', $containerName);
$containerCrawler = $crawler->filter($containerSelector);

return array_map(static function (\DOMNode $steamWorkshopItemNode) {
$steamWorkshopItemNodeCrawler = (new Crawler($steamWorkshopItemNode));

return [
'name' => $steamWorkshopItemNodeCrawler->filter('[data-type="DisplayName"]')->html(),
'url' => $steamWorkshopItemNodeCrawler->filter('[data-type="Link"]')->attr('href'),
];
}, iterator_to_array($containerCrawler->getIterator()));
};

$this->seeHttpHeader('Content-Disposition', sprintf('attachment; filename="%s"', $fileName));

$crawler = new Crawler($this->grabResponse());
$includedDlcs = $extractSteamWorkshopItems($crawler, 'DlcContainer');
$includedMods = $extractSteamWorkshopItems($crawler, 'ModContainer');

$expectedDlcs = array_map(static function (Dlc $dlc) {
return [
'name' => $dlc->getName(),
'url' => "https://store.steampowered.com/app/{$dlc->getAppId()}",
];
}, $expectedDlcs);

$expectedMods = array_map(static function (SteamWorkshopMod $steamWorkshopMod) {
return [
'name' => $steamWorkshopMod->getName(),
'url' => "https://steamcommunity.com/sharedfiles/filedetails/?id={$steamWorkshopMod->getItemId()}",
];
}, $expectedMods);

$this->assertSame($expectedDlcs, $includedDlcs);
$this->assertSame($expectedMods, $includedMods);
}
}
40 changes: 40 additions & 0 deletions tests/_support/Traits/SecurityAssertsTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace App\Tests\Traits;

use App\Entity\User\User;

trait SecurityAssertsTrait
{
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 amApiKeyAuthenticatedAs(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;
}
}
1 change: 0 additions & 1 deletion tests/functional.suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@ modules:
url: /
depends: Symfony
- Asserts
- \App\Tests\Helper\Functional
9 changes: 9 additions & 0 deletions tests/functional/Web/ModListPublic/CustomizeModListCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace App\Tests\Functional\Web\ModListPublic;

class CustomizeModListCest
{
}
Loading

0 comments on commit 07161c4

Please sign in to comment.