new: pkg: add test cases to back-end w/ real database connection in it #10
This commit is contained in:
145
tests/Controller/GameControllerTest.php
Normal file
145
tests/Controller/GameControllerTest.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
||||
use App\Tests\WebTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Class GameControllerTest
|
||||
*
|
||||
* @package App\Tests\Controller
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*/
|
||||
#[TestDox('Game Controller')]
|
||||
class GameControllerTest extends WebTestCase
|
||||
{
|
||||
private KernelBrowser $client;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->client = static::createClient();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Homepage loads successfully with navigation links')]
|
||||
public function homepageLoadsSuccessfully(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertSelectorExists('h1');
|
||||
self::assertSelectorExists('a[href="/play"]');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Play page loads successfully')]
|
||||
public function playPageLoadsSuccessfully(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/play');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertResponseHasHeader('Content-Type');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Play page with game association loads successfully')]
|
||||
public function playPageWithGameAssocLoadsSuccessfully(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/play/testgame123');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertResponseHasHeader('Content-Type');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Privacy policy page loads successfully')]
|
||||
public function privacyPolicyPageLoadsSuccessfully(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/privacy-policy');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertSelectorExists('h1');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Terms of service page loads successfully')]
|
||||
public function termsOfServicePageLoadsSuccessfully(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/terms-of-service');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertSelectorExists('h1');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Contact page loads successfully with form')]
|
||||
public function contactPageLoadsSuccessfully(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/contact');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertSelectorExists('form');
|
||||
self::assertSelectorExists('button[type="submit"]');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Landing page loads successfully with play link')]
|
||||
public function landingPageLoadsSuccessfully(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/landing-page');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertSelectorExists('h1');
|
||||
self::assertSelectorExists('a[href="/play"]');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Rules page loads successfully')]
|
||||
public function rulesPageLoadsSuccessfully(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/rules');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertSelectorExists('h1');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Homepage contains navigation links to play, login, and register')]
|
||||
public function homepageContainsNavigationLinks(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorExists('a[href="/play"]');
|
||||
self::assertSelectorExists('a[href="/login"]');
|
||||
self::assertSelectorExists('a[href="/register"]');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Play page has correct meta tags for SEO and social sharing')]
|
||||
public function playPageHasCorrectMetaTags(): void
|
||||
{
|
||||
$this->client->request('GET', '/play');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorExists('meta[name="description"]');
|
||||
self::assertSelectorExists('meta[property="og:title"]');
|
||||
}
|
||||
}
|
||||
146
tests/Controller/ProfileControllerTest.php
Normal file
146
tests/Controller/ProfileControllerTest.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Tests\Factory\PlayedGameFactory;
|
||||
use App\Tests\Factory\UserFactory;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
||||
use App\Tests\WebTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Class ProfileControllerTest
|
||||
*
|
||||
* @package App\Tests\Controller
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*/
|
||||
#[TestDox('Profile Controller')]
|
||||
class ProfileControllerTest extends WebTestCase
|
||||
{
|
||||
private KernelBrowser $client;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->client = static::createClient();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Profile page requires authentication')]
|
||||
public function profilePageRequiresAuthentication(): void
|
||||
{
|
||||
$this->client->request('GET', '/profile');
|
||||
|
||||
self::assertResponseRedirects('/login');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Profile security page requires authentication')]
|
||||
public function profileSecurityPageRequiresAuthentication(): void
|
||||
{
|
||||
$this->client->request('GET', '/profile/security');
|
||||
|
||||
self::assertResponseRedirects('/login');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Profile avatar upload requires authentication')]
|
||||
public function profileAvatarPageRequiresAuthentication(): void
|
||||
{
|
||||
$this->client->request('POST', '/profile/avatar');
|
||||
|
||||
self::assertResponseRedirects('/login');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Battle share page returns 404 for non-existent game')]
|
||||
public function battleSharePageReturns404ForNonExistentGame(): void
|
||||
{
|
||||
$uuid = '00000000-0000-4000-a000-000000000000';
|
||||
$this->client->request('GET', '/battle/' . $uuid);
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Battle share page displays valid game details')]
|
||||
public function battleSharePageShowsValidGame(): void
|
||||
{
|
||||
$game = PlayedGameFactory::new()
|
||||
->withRegisteredPlayers()
|
||||
->redWins()
|
||||
->create();
|
||||
|
||||
$crawler = $this->client->request('GET', '/battle/' . $game->uuid);
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorExists('h1');
|
||||
self::assertGreaterThan(0, $crawler->filter('body')->count());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Battle share page has Open Graph meta tags for social sharing')]
|
||||
public function battleSharePageHasOgMetaTags(): void
|
||||
{
|
||||
$game = PlayedGameFactory::new()
|
||||
->withRegisteredPlayers()
|
||||
->redWins()
|
||||
->create();
|
||||
|
||||
$this->client->request('GET', '/battle/' . $game->uuid);
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorExists('meta[property="og:title"]');
|
||||
self::assertSelectorExists('meta[property="og:image"]');
|
||||
self::assertSelectorExists('meta[property="og:description"]');
|
||||
self::assertSelectorExists('meta[property="og:type"]');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('OG battle image returns 404 for non-existent game')]
|
||||
public function ogBattleImageReturns404ForNonExistentGame(): void
|
||||
{
|
||||
$uuid = '00000000-0000-4000-a000-000000000000';
|
||||
$this->client->request('GET', '/og/battle/' . $uuid . '.png');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('OG battle image generates valid PNG for existing game')]
|
||||
public function ogBattleImageReturnsImageForValidGame(): void
|
||||
{
|
||||
$game = PlayedGameFactory::new()
|
||||
->withRegisteredPlayers()
|
||||
->redWins()
|
||||
->create();
|
||||
|
||||
$this->client->request('GET', '/og/battle/' . $game->uuid . '.png');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertResponseHeaderSame('Content-Type', 'image/png');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Battle share page returns 404 for invalid UUID format')]
|
||||
public function battleSharePageWithInvalidUuidFormat(): void
|
||||
{
|
||||
$this->client->request('GET', '/battle/invalid-uuid');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
148
tests/Controller/SecurityControllerTest.php
Normal file
148
tests/Controller/SecurityControllerTest.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Tests\Factory\UserFactory;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
||||
use App\Tests\WebTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Class SecurityControllerTest
|
||||
*
|
||||
* @package App\Tests\Controller
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*/
|
||||
#[TestDox('Security Controller')]
|
||||
class SecurityControllerTest extends WebTestCase
|
||||
{
|
||||
private KernelBrowser $client;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->client = static::createClient();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Login page loads successfully with form fields')]
|
||||
public function loginPageLoadsSuccessfully(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/login');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertSelectorExists('form');
|
||||
self::assertSelectorExists('input[name="_username"]');
|
||||
self::assertSelectorExists('input[name="_password"]');
|
||||
self::assertSelectorExists('button[type="submit"]');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Login page has links to register and forgot password')]
|
||||
public function loginPageHasRegisterLink(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/login');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorExists('a[href="/register"]');
|
||||
self::assertSelectorExists('a[href="/forgot-password"]');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Register page loads successfully with form')]
|
||||
public function registerPageLoadsSuccessfully(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/register');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertSelectorExists('form');
|
||||
self::assertSelectorExists('button[type="submit"]');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Register page has link to login')]
|
||||
public function registerPageHasLoginLink(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/register');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorExists('a[href="/login"]');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Forgot password page loads successfully with form')]
|
||||
public function forgotPasswordPageLoadsSuccessfully(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/forgot-password');
|
||||
|
||||
self::assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
self::assertSelectorExists('form');
|
||||
self::assertSelectorExists('button[type="submit"]');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Account activation with invalid token redirects to login')]
|
||||
public function activateWithInvalidTokenShowsError(): void
|
||||
{
|
||||
$this->client->request('GET', '/activate/invalid-token');
|
||||
|
||||
self::assertResponseRedirects('/login');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Password reset with invalid token redirects to forgot password')]
|
||||
public function resetPasswordWithInvalidTokenShowsError(): void
|
||||
{
|
||||
$this->client->request('GET', '/reset-password/invalid-token');
|
||||
|
||||
self::assertResponseRedirects('/forgot-password');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Authenticated user is redirected from login page to homepage')]
|
||||
public function authenticatedUserRedirectsFromLogin(): void
|
||||
{
|
||||
$user = UserFactory::createOne();
|
||||
$this->client->loginUser($user->_real());
|
||||
|
||||
$this->client->request('GET', '/login');
|
||||
|
||||
self::assertResponseRedirects('/');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Authenticated user is redirected from register page to homepage')]
|
||||
public function authenticatedUserRedirectsFromRegister(): void
|
||||
{
|
||||
$user = UserFactory::createOne();
|
||||
$this->client->loginUser($user->_real());
|
||||
|
||||
$this->client->request('GET', '/register');
|
||||
|
||||
self::assertResponseRedirects('/');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Login form has remember me checkbox')]
|
||||
public function loginFormHasRememberMeCheckbox(): void
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/login');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorExists('input[name="_remember_me"]');
|
||||
}
|
||||
}
|
||||
89
tests/Dto/ProfileChartDataDtoTest.php
Normal file
89
tests/Dto/ProfileChartDataDtoTest.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Dto;
|
||||
|
||||
use App\Dto\ProfileChartDataDto;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Class ProfileChartDataDtoTest
|
||||
*
|
||||
* @package App\Tests\Dto
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*/
|
||||
#[TestDox('Profile Chart Data Dto')]
|
||||
class ProfileChartDataDtoTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
#[TestDox('Json serialize returns all properties')]
|
||||
public function jsonSerializeReturnsAllProperties(): void
|
||||
{
|
||||
$months = ['Jan', 'Feb', 'Mar'];
|
||||
$wins = [5, 8, 10];
|
||||
$losses = [2, 3, 4];
|
||||
$draws = [1, 0, 1];
|
||||
$recentGames = [
|
||||
['id' => 1, 'result' => 'win'],
|
||||
['id' => 2, 'result' => 'loss'],
|
||||
];
|
||||
|
||||
$dto = new ProfileChartDataDto(
|
||||
months: $months,
|
||||
wins: $wins,
|
||||
losses: $losses,
|
||||
draws: $draws,
|
||||
pieWins: 23,
|
||||
pieLosses: 9,
|
||||
pieDraws: 2,
|
||||
recentGames: $recentGames,
|
||||
);
|
||||
|
||||
$json = $dto->jsonSerialize();
|
||||
|
||||
$this->assertSame($months, $json['months']);
|
||||
$this->assertSame($wins, $json['wins']);
|
||||
$this->assertSame($losses, $json['losses']);
|
||||
$this->assertSame($draws, $json['draws']);
|
||||
$this->assertSame(23, $json['pieWins']);
|
||||
$this->assertSame(9, $json['pieLosses']);
|
||||
$this->assertSame(2, $json['pieDraws']);
|
||||
$this->assertSame($recentGames, $json['recentGames']);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Constructor with empty arrays')]
|
||||
public function constructorWithEmptyArrays(): void
|
||||
{
|
||||
$dto = new ProfileChartDataDto(
|
||||
months: [],
|
||||
wins: [],
|
||||
losses: [],
|
||||
draws: [],
|
||||
pieWins: 0,
|
||||
pieLosses: 0,
|
||||
pieDraws: 0,
|
||||
recentGames: [],
|
||||
);
|
||||
|
||||
$json = $dto->jsonSerialize();
|
||||
|
||||
$this->assertSame([], $json['months']);
|
||||
$this->assertSame([], $json['wins']);
|
||||
$this->assertSame([], $json['recentGames']);
|
||||
$this->assertSame(0, $json['pieWins']);
|
||||
}
|
||||
}
|
||||
126
tests/Dto/ProfileGameDtoTest.php
Normal file
126
tests/Dto/ProfileGameDtoTest.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Dto;
|
||||
|
||||
use App\Dto\ProfileGameDto;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Class ProfileGameDtoTest
|
||||
*
|
||||
* @package App\Tests\Dto
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*/
|
||||
#[TestDox('Profile Game Dto')]
|
||||
class ProfileGameDtoTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
#[TestDox('Json serialize returns all properties')]
|
||||
public function jsonSerializeReturnsAllProperties(): void
|
||||
{
|
||||
$dto = new ProfileGameDto(
|
||||
id: 1,
|
||||
uuid: '550e8400-e29b-41d4-a716-446655440000',
|
||||
redName: 'RedPlayer',
|
||||
blueName: 'BluePlayer',
|
||||
redAvatar: '/uploads/avatars/red.png',
|
||||
blueAvatar: '/uploads/avatars/blue.png',
|
||||
redPoints: 10,
|
||||
bluePoints: 8,
|
||||
redExplodedBomb: false,
|
||||
blueExplodedBomb: true,
|
||||
resign: null,
|
||||
created: '2026-04-20 10:00',
|
||||
date: '2026-04-20 10:30',
|
||||
isRed: true,
|
||||
result: 'win',
|
||||
myPoints: 10,
|
||||
oppPoints: 8,
|
||||
redBonusPoints: 5.5,
|
||||
blueBonusPoints: 2.0,
|
||||
redBonusStats: ['blindHits' => 2, 'chainBest' => 3],
|
||||
blueBonusStats: ['blindHits' => 1, 'chainBest' => 2],
|
||||
bothRegistered: true,
|
||||
);
|
||||
|
||||
$json = $dto->jsonSerialize();
|
||||
|
||||
$this->assertSame(1, $json['id']);
|
||||
$this->assertSame('550e8400-e29b-41d4-a716-446655440000', $json['uuid']);
|
||||
$this->assertSame('RedPlayer', $json['redName']);
|
||||
$this->assertSame('BluePlayer', $json['blueName']);
|
||||
$this->assertSame('/uploads/avatars/red.png', $json['redAvatar']);
|
||||
$this->assertSame('/uploads/avatars/blue.png', $json['blueAvatar']);
|
||||
$this->assertSame(10, $json['redPoints']);
|
||||
$this->assertSame(8, $json['bluePoints']);
|
||||
$this->assertFalse($json['redExplodedBomb']);
|
||||
$this->assertTrue($json['blueExplodedBomb']);
|
||||
$this->assertNull($json['resign']);
|
||||
$this->assertSame('2026-04-20 10:00', $json['created']);
|
||||
$this->assertSame('2026-04-20 10:30', $json['date']);
|
||||
$this->assertTrue($json['isRed']);
|
||||
$this->assertSame('win', $json['result']);
|
||||
$this->assertSame(10, $json['myPoints']);
|
||||
$this->assertSame(8, $json['oppPoints']);
|
||||
$this->assertSame(5.5, $json['redBonusPoints']);
|
||||
$this->assertSame(2.0, $json['blueBonusPoints']);
|
||||
$this->assertSame(['blindHits' => 2, 'chainBest' => 3], $json['redBonusStats']);
|
||||
$this->assertSame(['blindHits' => 1, 'chainBest' => 2], $json['blueBonusStats']);
|
||||
$this->assertTrue($json['bothRegistered']);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Json serialize with null values')]
|
||||
public function jsonSerializeWithNullValues(): void
|
||||
{
|
||||
$dto = new ProfileGameDto(
|
||||
id: null,
|
||||
uuid: null,
|
||||
redName: 'Guest',
|
||||
blueName: 'Guest',
|
||||
redAvatar: null,
|
||||
blueAvatar: null,
|
||||
redPoints: null,
|
||||
bluePoints: null,
|
||||
redExplodedBomb: null,
|
||||
blueExplodedBomb: null,
|
||||
resign: null,
|
||||
created: null,
|
||||
date: null,
|
||||
isRed: false,
|
||||
result: 'draw',
|
||||
myPoints: null,
|
||||
oppPoints: null,
|
||||
redBonusPoints: 0.0,
|
||||
blueBonusPoints: 0.0,
|
||||
redBonusStats: [],
|
||||
blueBonusStats: [],
|
||||
bothRegistered: false,
|
||||
);
|
||||
|
||||
$json = $dto->jsonSerialize();
|
||||
|
||||
$this->assertNull($json['id']);
|
||||
$this->assertNull($json['uuid']);
|
||||
$this->assertNull($json['redAvatar']);
|
||||
$this->assertNull($json['blueAvatar']);
|
||||
$this->assertNull($json['redPoints']);
|
||||
$this->assertNull($json['bluePoints']);
|
||||
$this->assertSame('draw', $json['result']);
|
||||
$this->assertFalse($json['bothRegistered']);
|
||||
}
|
||||
}
|
||||
134
tests/Dto/ProfileStatsDtoTest.php
Normal file
134
tests/Dto/ProfileStatsDtoTest.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Dto;
|
||||
|
||||
use App\Dto\ProfileStatsDto;
|
||||
use App\Entity\UserStats;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Class ProfileStatsDtoTest
|
||||
*
|
||||
* @package App\Tests\Dto
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*/
|
||||
#[TestDox('Profile Stats Dto')]
|
||||
class ProfileStatsDtoTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
#[TestDox('From user stats with valid stats')]
|
||||
public function fromUserStatsWithValidStats(): void
|
||||
{
|
||||
$userStats = new UserStats();
|
||||
$userStats->userId = 1;
|
||||
$userStats->totalGames = 100;
|
||||
$userStats->wins = 60;
|
||||
$userStats->losses = 30;
|
||||
$userStats->draws = 10;
|
||||
$userStats->totalMines = 500;
|
||||
$userStats->totalBonusPoints = '150.5';
|
||||
$userStats->avgBonus = '2.5';
|
||||
$userStats->bestChain = 5;
|
||||
$userStats->blindHits = 20;
|
||||
$userStats->edgeMines = 15;
|
||||
$userStats->gamesWithScores = 90;
|
||||
|
||||
$dto = ProfileStatsDto::fromUserStats($userStats);
|
||||
|
||||
$this->assertSame(100, $dto->total);
|
||||
$this->assertSame(60, $dto->wins);
|
||||
$this->assertSame(30, $dto->losses);
|
||||
$this->assertSame(10, $dto->draws);
|
||||
$this->assertSame(500, $dto->minesHit);
|
||||
$this->assertSame(67, $dto->winRate); // 60/90 * 100 = 67
|
||||
$this->assertSame(6, $dto->avgScore); // 500/90 = 5.56 -> 6
|
||||
$this->assertSame(150.5, $dto->bonusPoints);
|
||||
$this->assertSame(2.5, $dto->avgBonus);
|
||||
$this->assertSame(5, $dto->bestChain);
|
||||
$this->assertSame(20, $dto->blindHits);
|
||||
$this->assertSame(15, $dto->edgeMines);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('From user stats with null returns empty')]
|
||||
public function fromUserStatsWithNullReturnsEmpty(): void
|
||||
{
|
||||
$dto = ProfileStatsDto::fromUserStats(null);
|
||||
|
||||
$this->assertSame(0, $dto->total);
|
||||
$this->assertSame(0, $dto->wins);
|
||||
$this->assertSame(0, $dto->losses);
|
||||
$this->assertSame(0, $dto->draws);
|
||||
$this->assertSame(0, $dto->minesHit);
|
||||
$this->assertSame(0, $dto->winRate);
|
||||
$this->assertSame(0, $dto->avgScore);
|
||||
$this->assertSame(0.0, $dto->bonusPoints);
|
||||
$this->assertSame(0.0, $dto->avgBonus);
|
||||
$this->assertSame(0, $dto->bestChain);
|
||||
$this->assertSame(0, $dto->blindHits);
|
||||
$this->assertSame(0, $dto->edgeMines);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Empty returns default values')]
|
||||
public function emptyReturnsDefaultValues(): void
|
||||
{
|
||||
$dto = ProfileStatsDto::empty();
|
||||
|
||||
$this->assertSame(0, $dto->total);
|
||||
$this->assertSame(0, $dto->wins);
|
||||
$this->assertSame(0, $dto->losses);
|
||||
$this->assertSame(0, $dto->draws);
|
||||
$this->assertSame(0, $dto->minesHit);
|
||||
$this->assertSame(0, $dto->winRate);
|
||||
$this->assertSame(0, $dto->avgScore);
|
||||
$this->assertSame(0.0, $dto->bonusPoints);
|
||||
$this->assertSame(0.0, $dto->avgBonus);
|
||||
$this->assertSame(0, $dto->bestChain);
|
||||
$this->assertSame(0, $dto->blindHits);
|
||||
$this->assertSame(0, $dto->edgeMines);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Win rate calculation with no games with scores')]
|
||||
public function winRateCalculationWithNoGamesWithScores(): void
|
||||
{
|
||||
$userStats = new UserStats();
|
||||
$userStats->totalGames = 10;
|
||||
$userStats->wins = 5;
|
||||
$userStats->losses = 5;
|
||||
$userStats->draws = 0;
|
||||
$userStats->gamesWithScores = 0;
|
||||
|
||||
$dto = ProfileStatsDto::fromUserStats($userStats);
|
||||
|
||||
$this->assertSame(0, $dto->winRate);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Avg score calculation with no games with scores')]
|
||||
public function avgScoreCalculationWithNoGamesWithScores(): void
|
||||
{
|
||||
$userStats = new UserStats();
|
||||
$userStats->totalMines = 100;
|
||||
$userStats->gamesWithScores = 0;
|
||||
|
||||
$dto = ProfileStatsDto::fromUserStats($userStats);
|
||||
|
||||
$this->assertSame(0, $dto->avgScore);
|
||||
}
|
||||
}
|
||||
142
tests/Entity/UserStatsTest.php
Normal file
142
tests/Entity/UserStatsTest.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\UserStats;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Class UserStatsTest
|
||||
*
|
||||
* @package App\Tests\Entity
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*/
|
||||
#[TestDox('User Stats')]
|
||||
class UserStatsTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
#[TestDox('Get win rate calculates correctly')]
|
||||
public function getWinRateCalculatesCorrectly(): void
|
||||
{
|
||||
$stats = new UserStats();
|
||||
$stats->wins = 60;
|
||||
$stats->gamesWithScores = 100;
|
||||
|
||||
$result = $stats->getWinRate();
|
||||
|
||||
$this->assertSame(60, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Get win rate with zero games returns zero')]
|
||||
public function getWinRateWithZeroGamesReturnsZero(): void
|
||||
{
|
||||
$stats = new UserStats();
|
||||
$stats->wins = 10;
|
||||
$stats->gamesWithScores = 0;
|
||||
|
||||
$result = $stats->getWinRate();
|
||||
|
||||
$this->assertSame(0, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Get win rate rounds correctly')]
|
||||
public function getWinRateRoundsCorrectly(): void
|
||||
{
|
||||
$stats = new UserStats();
|
||||
$stats->wins = 33;
|
||||
$stats->gamesWithScores = 100;
|
||||
|
||||
$result = $stats->getWinRate();
|
||||
|
||||
$this->assertSame(33, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Get avg score calculates correctly')]
|
||||
public function getAvgScoreCalculatesCorrectly(): void
|
||||
{
|
||||
$stats = new UserStats();
|
||||
$stats->totalMines = 550;
|
||||
$stats->gamesWithScores = 100;
|
||||
|
||||
$result = $stats->getAvgScore();
|
||||
|
||||
$this->assertSame(6, $result); // 550/100 = 5.5 -> 6
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Get avg score with zero games returns zero')]
|
||||
public function getAvgScoreWithZeroGamesReturnsZero(): void
|
||||
{
|
||||
$stats = new UserStats();
|
||||
$stats->totalMines = 100;
|
||||
$stats->gamesWithScores = 0;
|
||||
|
||||
$result = $stats->getAvgScore();
|
||||
|
||||
$this->assertSame(0, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Get avg score rounds down')]
|
||||
public function getAvgScoreRoundsDown(): void
|
||||
{
|
||||
$stats = new UserStats();
|
||||
$stats->totalMines = 101;
|
||||
$stats->gamesWithScores = 100;
|
||||
|
||||
$result = $stats->getAvgScore();
|
||||
|
||||
$this->assertSame(1, $result); // 101/100 = 1.01 -> 1
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Get avg score rounds up')]
|
||||
public function getAvgScoreRoundsUp(): void
|
||||
{
|
||||
$stats = new UserStats();
|
||||
$stats->totalMines = 151;
|
||||
$stats->gamesWithScores = 100;
|
||||
|
||||
$result = $stats->getAvgScore();
|
||||
|
||||
$this->assertSame(2, $result); // 151/100 = 1.51 -> 2
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Default values')]
|
||||
public function defaultValues(): void
|
||||
{
|
||||
$stats = new UserStats();
|
||||
|
||||
$this->assertSame(0, $stats->userId);
|
||||
$this->assertSame(0, $stats->totalGames);
|
||||
$this->assertSame(0, $stats->wins);
|
||||
$this->assertSame(0, $stats->losses);
|
||||
$this->assertSame(0, $stats->draws);
|
||||
$this->assertSame(0, $stats->totalMines);
|
||||
$this->assertSame('0.0', $stats->totalBonusPoints);
|
||||
$this->assertSame('0.0', $stats->avgBonus);
|
||||
$this->assertSame(0, $stats->bestChain);
|
||||
$this->assertSame(0, $stats->blindHits);
|
||||
$this->assertSame(0, $stats->edgeMines);
|
||||
$this->assertSame(0, $stats->gamesWithScores);
|
||||
$this->assertNull($stats->lastGameAt);
|
||||
}
|
||||
}
|
||||
55
tests/Factory/ContactMessageFactory.php
Normal file
55
tests/Factory/ContactMessageFactory.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\ContactMessage;
|
||||
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
|
||||
|
||||
/**
|
||||
* Class ContactMessageFactory
|
||||
*
|
||||
* @package App\Tests\Factory
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*
|
||||
* @extends PersistentProxyObjectFactory<ContactMessage>
|
||||
*/
|
||||
class ContactMessageFactory extends PersistentProxyObjectFactory
|
||||
{
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'name' => self::faker()->name(),
|
||||
'email' => self::faker()->safeEmail(),
|
||||
'content' => self::faker()->paragraph(3),
|
||||
'consent' => true,
|
||||
'ipAddress' => self::faker()->ipv4(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return ContactMessage::class;
|
||||
}
|
||||
|
||||
public function withoutConsent(): self
|
||||
{
|
||||
return $this->with(['consent' => false]);
|
||||
}
|
||||
|
||||
public function anonymous(): self
|
||||
{
|
||||
return $this->with(['ipAddress' => null]);
|
||||
}
|
||||
}
|
||||
51
tests/Factory/GamerFactory.php
Normal file
51
tests/Factory/GamerFactory.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\Gamer;
|
||||
use DateTime;
|
||||
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
|
||||
|
||||
/**
|
||||
* Class GamerFactory
|
||||
*
|
||||
* @package App\Tests\Factory
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*
|
||||
* @extends PersistentProxyObjectFactory<Gamer>
|
||||
*/
|
||||
class GamerFactory extends PersistentProxyObjectFactory
|
||||
{
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'userName' => self::faker()->userName(),
|
||||
'ip' => self::faker()->ipv4(),
|
||||
'country' => self::faker()->countryCode(),
|
||||
'userAgent' => self::faker()->userAgent(),
|
||||
'connTimestamp' => new DateTime(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return Gamer::class;
|
||||
}
|
||||
|
||||
public function anonymous(): self
|
||||
{
|
||||
return $this->with(['userName' => sprintf('Guest_%d', self::faker()->randomNumber(5))]);
|
||||
}
|
||||
}
|
||||
53
tests/Factory/GridFactory.php
Normal file
53
tests/Factory/GridFactory.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\Grid;
|
||||
use App\Entity\GridRow;
|
||||
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
|
||||
|
||||
/**
|
||||
* Class GridFactory
|
||||
*
|
||||
* @package App\Tests\Factory
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*
|
||||
* @extends PersistentProxyObjectFactory<Grid>
|
||||
*/
|
||||
class GridFactory extends PersistentProxyObjectFactory
|
||||
{
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'playedGame' => PlayedGameFactory::new(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return Grid::class;
|
||||
}
|
||||
|
||||
protected function initialize(): static
|
||||
{
|
||||
return $this->afterInstantiate(function (Grid $grid): void {
|
||||
for ($i = 0; $i < 16; $i++) {
|
||||
/** @var GridRow $factory */
|
||||
$factory = GridRowFactory::new()->create()->_real();
|
||||
$grid->addGridRow($factory);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
52
tests/Factory/GridRowFactory.php
Normal file
52
tests/Factory/GridRowFactory.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\GridRow;
|
||||
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
|
||||
|
||||
/**
|
||||
* Class GridRowFactory
|
||||
*
|
||||
* @package App\Tests\Factory
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*
|
||||
* @extends PersistentProxyObjectFactory<GridRow>
|
||||
*/
|
||||
class GridRowFactory extends PersistentProxyObjectFactory
|
||||
{
|
||||
protected function defaults(): array
|
||||
{
|
||||
$columns = [];
|
||||
|
||||
for ($i = 0; $i < 16; $i++) {
|
||||
$columns[] = self::faker()->numberBetween(0, 8);
|
||||
}
|
||||
|
||||
return [
|
||||
'gridCol' => $columns,
|
||||
];
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return GridRow::class;
|
||||
}
|
||||
|
||||
public function withColumns(array $columns): self
|
||||
{
|
||||
return $this->with(['gridCol' => $columns]);
|
||||
}
|
||||
}
|
||||
108
tests/Factory/PlayedGameFactory.php
Normal file
108
tests/Factory/PlayedGameFactory.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\PlayedGame;
|
||||
use DateTime;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
|
||||
|
||||
/**
|
||||
* Class PlayedGameFactory
|
||||
*
|
||||
* @package App\Tests\Factory
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*
|
||||
* @extends PersistentProxyObjectFactory<PlayedGame>
|
||||
*/
|
||||
class PlayedGameFactory extends PersistentProxyObjectFactory
|
||||
{
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'uuid' => Uuid::v4(),
|
||||
'gameAssoc' => self::faker()->uuid(),
|
||||
'redPoints' => self::faker()->numberBetween(0, 51),
|
||||
'bluePoints' => self::faker()->numberBetween(0, 51),
|
||||
'redExplodedBomb' => false,
|
||||
'blueExplodedBomb' => false,
|
||||
'resign' => null,
|
||||
'redBonusPoints' => self::faker()->randomFloat(2, 0, 100),
|
||||
'blueBonusPoints' => self::faker()->randomFloat(2, 0, 100),
|
||||
'redBonusStats' => null,
|
||||
'blueBonusStats' => null,
|
||||
'created' => new DateTime(),
|
||||
'updated' => new DateTime(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return PlayedGame::class;
|
||||
}
|
||||
|
||||
public function withRegisteredPlayers(): self
|
||||
{
|
||||
return $this->with([
|
||||
'red' => UserFactory::new(),
|
||||
'blue' => UserFactory::new(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function withAnonymousPlayers(): self
|
||||
{
|
||||
return $this->with([
|
||||
'redAnon' => GamerFactory::new(),
|
||||
'blueAnon' => GamerFactory::new(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function withMixedPlayers(): self
|
||||
{
|
||||
return $this->with([
|
||||
'red' => UserFactory::new(),
|
||||
'blueAnon' => GamerFactory::new(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function finished(): self
|
||||
{
|
||||
return $this->with([
|
||||
'redPoints' => 26,
|
||||
'bluePoints' => 25,
|
||||
]);
|
||||
}
|
||||
|
||||
public function redWins(): self
|
||||
{
|
||||
return $this->with([
|
||||
'redPoints' => 26,
|
||||
'bluePoints' => self::faker()->numberBetween(0, 25),
|
||||
]);
|
||||
}
|
||||
|
||||
public function blueWins(): self
|
||||
{
|
||||
return $this->with([
|
||||
'redPoints' => self::faker()->numberBetween(0, 25),
|
||||
'bluePoints' => 26,
|
||||
]);
|
||||
}
|
||||
|
||||
public function resigned(string $player): self
|
||||
{
|
||||
return $this->with(['resign' => $player]);
|
||||
}
|
||||
}
|
||||
68
tests/Factory/StepFactory.php
Normal file
68
tests/Factory/StepFactory.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\Step;
|
||||
use DateTime;
|
||||
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
|
||||
|
||||
/**
|
||||
* Class StepFactory
|
||||
*
|
||||
* @package App\Tests\Factory
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*
|
||||
* @extends PersistentProxyObjectFactory<Step>
|
||||
*/
|
||||
class StepFactory extends PersistentProxyObjectFactory
|
||||
{
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'row' => self::faker()->numberBetween(0, 15),
|
||||
'col' => self::faker()->numberBetween(0, 15),
|
||||
'wBomb' => self::faker()->boolean(),
|
||||
'player' => self::faker()->randomElement(['red', 'blue']),
|
||||
'revealedCells' => null,
|
||||
'playedGame' => PlayedGameFactory::new(),
|
||||
'created' => new DateTime(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return Step::class;
|
||||
}
|
||||
|
||||
public function mine(): self
|
||||
{
|
||||
return $this->with(['wBomb' => true]);
|
||||
}
|
||||
|
||||
public function safe(): self
|
||||
{
|
||||
return $this->with(['wBomb' => false]);
|
||||
}
|
||||
|
||||
public function forPlayer(string $player): self
|
||||
{
|
||||
return $this->with(['player' => $player]);
|
||||
}
|
||||
|
||||
public function withRevealedCells(array $cells): self
|
||||
{
|
||||
return $this->with(['revealedCells' => $cells]);
|
||||
}
|
||||
}
|
||||
60
tests/Factory/UserFactory.php
Normal file
60
tests/Factory/UserFactory.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\User;
|
||||
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
|
||||
|
||||
/**
|
||||
* Class UserFactory
|
||||
*
|
||||
* @package App\Tests\Factory
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*
|
||||
* @extends PersistentProxyObjectFactory<User>
|
||||
*/
|
||||
class UserFactory extends PersistentProxyObjectFactory
|
||||
{
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'username' => self::faker()->unique()->userName(),
|
||||
'email' => self::faker()->unique()->safeEmail(),
|
||||
'password' => 'hashedpassword',
|
||||
'isVerified' => true,
|
||||
'roles' => [],
|
||||
];
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return User::class;
|
||||
}
|
||||
|
||||
public function withPassword(string $hashedPassword): self
|
||||
{
|
||||
return $this->with(['password' => $hashedPassword]);
|
||||
}
|
||||
|
||||
public function unverified(): self
|
||||
{
|
||||
return $this->with(['isVerified' => false]);
|
||||
}
|
||||
|
||||
public function withRoles(array $roles): self
|
||||
{
|
||||
return $this->with(['roles' => $roles]);
|
||||
}
|
||||
}
|
||||
62
tests/Factory/WebAuthnCredentialFactory.php
Normal file
62
tests/Factory/WebAuthnCredentialFactory.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Factory;
|
||||
|
||||
use App\Entity\WebAuthnCredential;
|
||||
use DateTime;
|
||||
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
|
||||
|
||||
/**
|
||||
* Class WebAuthnCredentialFactory
|
||||
*
|
||||
* @package App\Tests\Factory
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*
|
||||
* @extends PersistentProxyObjectFactory<WebAuthnCredential>
|
||||
*/
|
||||
class WebAuthnCredentialFactory extends PersistentProxyObjectFactory
|
||||
{
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'user' => UserFactory::new(),
|
||||
'credentialData' => json_encode([
|
||||
'type' => 'public-key',
|
||||
'id' => base64_encode(self::faker()->uuid()),
|
||||
'transports' => ['usb', 'nfc'],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
'credentialName' => self::faker()->words(3, true),
|
||||
'createdAt' => new DateTime(),
|
||||
'lastUsedAt' => null,
|
||||
'isBackupEligible' => self::faker()->boolean(),
|
||||
'isBackupAuthenticated' => self::faker()->boolean(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function class(): string
|
||||
{
|
||||
return WebAuthnCredential::class;
|
||||
}
|
||||
|
||||
public function withName(string $name): self
|
||||
{
|
||||
return $this->with(['credentialName' => $name]);
|
||||
}
|
||||
|
||||
public function recentlyUsed(): self
|
||||
{
|
||||
return $this->with(['lastUsedAt' => new DateTime()]);
|
||||
}
|
||||
}
|
||||
211
tests/Integration/FactoryExampleTest.php
Normal file
211
tests/Integration/FactoryExampleTest.php
Normal file
@@ -0,0 +1,211 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Integration;
|
||||
|
||||
use App\Entity\PlayedGame;
|
||||
use App\Entity\User;
|
||||
use App\Tests\Factory\ContactMessageFactory;
|
||||
use App\Tests\Factory\GamerFactory;
|
||||
use App\Tests\Factory\GridFactory;
|
||||
use App\Tests\Factory\PlayedGameFactory;
|
||||
use App\Tests\Factory\StepFactory;
|
||||
use App\Tests\Factory\UserFactory;
|
||||
use App\Tests\Factory\WebAuthnCredentialFactory;
|
||||
use App\Tests\WebTestCase;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
|
||||
/**
|
||||
* Class FactoryExampleTest
|
||||
*
|
||||
* Example test demonstrating Foundry factory usage
|
||||
*
|
||||
* @package App\Tests\Integration
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*/
|
||||
#[TestDox('Factory Examples')]
|
||||
class FactoryExampleTest extends WebTestCase
|
||||
{
|
||||
#[Test]
|
||||
#[TestDox('Creates a verified user with UserFactory')]
|
||||
public function createUser(): void
|
||||
{
|
||||
$user = UserFactory::createOne();
|
||||
|
||||
self::assertInstanceOf(User::class, $user->_real());
|
||||
self::assertNotNull($user->username);
|
||||
self::assertNotNull($user->email);
|
||||
self::assertTrue($user->isVerified);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Creates an unverified user')]
|
||||
public function createUnverifiedUser(): void
|
||||
{
|
||||
$user = UserFactory::createOne(['isVerified' => false]);
|
||||
|
||||
self::assertFalse($user->isVerified);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Creates multiple users at once')]
|
||||
public function createMultipleUsers(): void
|
||||
{
|
||||
UserFactory::createMany(5);
|
||||
|
||||
self::assertCount(5, UserFactory::repository()->findAll());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Creates an anonymous gamer with guest username')]
|
||||
public function createAnonymousGamer(): void
|
||||
{
|
||||
$gamer = GamerFactory::new()->anonymous()->create();
|
||||
|
||||
self::assertStringStartsWith('Guest_', $gamer->userName);
|
||||
self::assertNotNull($gamer->ip);
|
||||
self::assertNotNull($gamer->connTimestamp);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Creates a game with two registered players')]
|
||||
public function createGameWithRegisteredPlayers(): void
|
||||
{
|
||||
$game = PlayedGameFactory::new()
|
||||
->withRegisteredPlayers()
|
||||
->create();
|
||||
|
||||
self::assertInstanceOf(PlayedGame::class, $game->_real());
|
||||
self::assertNotNull($game->red);
|
||||
self::assertNotNull($game->blue);
|
||||
self::assertNull($game->redAnon);
|
||||
self::assertNull($game->blueAnon);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Creates a game with two anonymous players')]
|
||||
public function createGameWithAnonymousPlayers(): void
|
||||
{
|
||||
$game = PlayedGameFactory::new()
|
||||
->withAnonymousPlayers()
|
||||
->create();
|
||||
|
||||
self::assertNull($game->red);
|
||||
self::assertNull($game->blue);
|
||||
self::assertNotNull($game->redAnon);
|
||||
self::assertNotNull($game->blueAnon);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Creates a finished game where red player wins')]
|
||||
public function createFinishedGame(): void
|
||||
{
|
||||
$game = PlayedGameFactory::new()
|
||||
->withRegisteredPlayers()
|
||||
->redWins()
|
||||
->create();
|
||||
|
||||
self::assertEquals(26, $game->redPoints);
|
||||
self::assertLessThan(26, $game->bluePoints);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Creates a game with multiple steps from both players')]
|
||||
public function createGameWithSteps(): void
|
||||
{
|
||||
$game = PlayedGameFactory::new()
|
||||
->withRegisteredPlayers()
|
||||
->create();
|
||||
|
||||
StepFactory::new()
|
||||
->forPlayer('red')
|
||||
->mine()
|
||||
->create(['playedGame' => $game]);
|
||||
|
||||
StepFactory::new()
|
||||
->forPlayer('red')
|
||||
->mine()
|
||||
->create(['playedGame' => $game]);
|
||||
|
||||
StepFactory::new()
|
||||
->forPlayer('red')
|
||||
->mine()
|
||||
->create(['playedGame' => $game]);
|
||||
|
||||
StepFactory::new()
|
||||
->forPlayer('blue')
|
||||
->safe()
|
||||
->create(['playedGame' => $game]);
|
||||
|
||||
StepFactory::new()
|
||||
->forPlayer('blue')
|
||||
->safe()
|
||||
->create(['playedGame' => $game]);
|
||||
|
||||
self::assertCount(5, StepFactory::repository()->findAll());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Creates a game with a 16x16 grid')]
|
||||
public function createGameWithGrid(): void
|
||||
{
|
||||
$game = PlayedGameFactory::new()
|
||||
->withRegisteredPlayers()
|
||||
->create();
|
||||
|
||||
$grid = GridFactory::createOne(['playedGame' => $game]);
|
||||
|
||||
self::assertCount(16, $grid->gridRow);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Creates a WebAuthn credential for a user')]
|
||||
public function createWebAuthnCredential(): void
|
||||
{
|
||||
$user = UserFactory::createOne();
|
||||
|
||||
$credential = WebAuthnCredentialFactory::new()
|
||||
->withName('YubiKey 5C')
|
||||
->recentlyUsed()
|
||||
->create(['user' => $user]);
|
||||
|
||||
self::assertEquals($user->_real(), $credential->user);
|
||||
self::assertEquals('YubiKey 5C', $credential->credentialName);
|
||||
self::assertNotNull($credential->lastUsedAt);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Creates a contact message with consent')]
|
||||
public function createContactMessage(): void
|
||||
{
|
||||
$message = ContactMessageFactory::createOne();
|
||||
|
||||
self::assertNotNull($message->name);
|
||||
self::assertNotNull($message->email);
|
||||
self::assertNotNull($message->content);
|
||||
self::assertTrue($message->consent);
|
||||
self::assertNotNull($message->createdAt);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Tests are isolated - database is reset between tests')]
|
||||
public function databaseIsolation(): void
|
||||
{
|
||||
UserFactory::createMany(3);
|
||||
self::assertCount(3, UserFactory::repository()->findAll());
|
||||
|
||||
/** Database will be reset before next test due to ResetDatabase trait */
|
||||
}
|
||||
}
|
||||
108
tests/README.md
Normal file
108
tests/README.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Tests Directory
|
||||
|
||||
This directory contains all test files for the MineSeeker project.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run all tests (recommended)
|
||||
make test
|
||||
|
||||
# Or with PHPUnit directly:
|
||||
vendor/bin/phpunit
|
||||
|
||||
# Run specific test file
|
||||
vendor/bin/phpunit tests/Controller/ProfileControllerTest.php
|
||||
|
||||
# Run with filter
|
||||
vendor/bin/phpunit --filter testCreateUser
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For comprehensive testing documentation, see:
|
||||
|
||||
- **[Testing Guide](../docs/testing/TESTING.md)** - Complete testing setup, best practices, troubleshooting
|
||||
- **[Factory Documentation](../docs/testing/FACTORIES.md)** - Detailed factory API reference
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── Controller/ # HTTP endpoint tests
|
||||
├── Dto/ # Data Transfer Object tests
|
||||
├── Entity/ # Entity logic tests
|
||||
├── Service/ # Service layer tests
|
||||
├── Integration/ # Integration tests (example: FactoryExampleTest.php)
|
||||
├── Factory/ # Foundry factory classes
|
||||
│ ├── UserFactory.php
|
||||
│ ├── GamerFactory.php
|
||||
│ ├── PlayedGameFactory.php
|
||||
│ ├── StepFactory.php
|
||||
│ ├── GridFactory.php
|
||||
│ ├── GridRowFactory.php
|
||||
│ ├── WebAuthnCredentialFactory.php
|
||||
│ └── ContactMessageFactory.php
|
||||
├── WebTestCase.php # Base test class (extends this!)
|
||||
├── bootstrap.php # PHPUnit bootstrap
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Quick Factory Examples
|
||||
|
||||
```php
|
||||
use App\Tests\Factory\UserFactory;
|
||||
use App\Tests\Factory\PlayedGameFactory;
|
||||
use App\Tests\WebTestCase;
|
||||
|
||||
class MyTest extends WebTestCase
|
||||
{
|
||||
public function testExample(): void
|
||||
{
|
||||
/** Create user */
|
||||
$user = UserFactory::createOne();
|
||||
|
||||
/** Create game with registered players */
|
||||
$game = PlayedGameFactory::new()
|
||||
->withRegisteredPlayers()
|
||||
->redWins()
|
||||
->create();
|
||||
|
||||
/** Create multiple entities */
|
||||
UserFactory::createMany(5);
|
||||
|
||||
/** Access repository */
|
||||
$users = UserFactory::repository()->findAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **Always extend `App\Tests\WebTestCase`** - provides database isolation
|
||||
- **Use factories** - don't manually create entities with `new Entity()`
|
||||
- **Test database** - Tests run on `mineseeker_test`, never production
|
||||
- **Automatic rollback** - Each test is wrapped in a transaction
|
||||
|
||||
## Test Database Setup (One-time)
|
||||
|
||||
```bash
|
||||
# Create test database
|
||||
bin/console dbal:run-sql "CREATE DATABASE mineseeker_test"
|
||||
|
||||
# Run migrations
|
||||
bin/console doctrine:migrations:migrate --env=test --no-interaction
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Tests interfering with each other?
|
||||
→ Make sure your test extends `App\Tests\WebTestCase`
|
||||
|
||||
### Database schema out of sync?
|
||||
→ Run `bin/console doctrine:migrations:migrate --env=test`
|
||||
|
||||
### Memory limit errors?
|
||||
→ Run `php -d memory_limit=512M vendor/bin/phpunit`
|
||||
|
||||
For more troubleshooting, see [Testing Guide](../docs/testing/TESTING.md#troubleshooting).
|
||||
109
tests/Service/MercureJwtServiceTest.php
Normal file
109
tests/Service/MercureJwtServiceTest.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\Service\MercureJwtService;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Class MercureJwtServiceTest
|
||||
*
|
||||
* @package App\Tests\Service
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*/
|
||||
#[TestDox('Mercure Jwt Service')]
|
||||
class MercureJwtServiceTest extends TestCase
|
||||
{
|
||||
/** JWT HS256 requires at least 32 characters for the secret key */
|
||||
private const SECRET = 'test-mercure-secret-key-12345678901234567890';
|
||||
|
||||
private MercureJwtService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->service = new MercureJwtService(self::SECRET);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Mint subscriber token returns valid jwt')]
|
||||
public function mintSubscriberTokenReturnsValidJwt(): void
|
||||
{
|
||||
$token = $this->service->mintSubscriberToken('game123', 'player1');
|
||||
|
||||
$this->assertIsString($token);
|
||||
$this->assertNotEmpty($token);
|
||||
|
||||
/** Token should have 3 parts (header.payload.signature) */
|
||||
$parts = explode('.', $token);
|
||||
$this->assertCount(3, $parts);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Mint subscriber token with different game assoc')]
|
||||
public function mintSubscriberTokenWithDifferentGameAssoc(): void
|
||||
{
|
||||
$token1 = $this->service->mintSubscriberToken('gameA', 'player1');
|
||||
$token2 = $this->service->mintSubscriberToken('gameB', 'player1');
|
||||
|
||||
/** Different gameAssoc should result in different tokens */
|
||||
$this->assertNotSame($token1, $token2);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Mint subscriber token with different user names')]
|
||||
public function mintSubscriberTokenWithDifferentUserNames(): void
|
||||
{
|
||||
$token1 = $this->service->mintSubscriberToken('game1', 'playerA');
|
||||
$token2 = $this->service->mintSubscriberToken('game1', 'playerB');
|
||||
|
||||
/** Different usernames should result in different tokens */
|
||||
$this->assertNotSame($token1, $token2);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Mint subscriber token contains proper structure')]
|
||||
public function mintSubscriberTokenContainsProperStructure(): void
|
||||
{
|
||||
$token = $this->service->mintSubscriberToken('game123', 'testplayer');
|
||||
|
||||
/** Decode without verification to check structure */
|
||||
$parts = explode('.', $token);
|
||||
|
||||
/** Decode payload (middle part) */
|
||||
$payload = json_decode(base64_decode($parts[1] . str_repeat('=', (4 - strlen($parts[1]) % 4))), true);
|
||||
|
||||
$this->assertIsArray($payload);
|
||||
$this->assertArrayHasKey('mercure', $payload);
|
||||
$this->assertArrayHasKey('subscribe', $payload['mercure']);
|
||||
$this->assertArrayHasKey('payload', $payload['mercure']);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Mint subscriber token payload contains correct data')]
|
||||
public function mintSubscriberTokenPayloadContainsCorrectData(): void
|
||||
{
|
||||
$token = $this->service->mintSubscriberToken('test-game', 'test-user');
|
||||
|
||||
$parts = explode('.', $token);
|
||||
$payload = json_decode(base64_decode($parts[1] . str_repeat('=', (4 - strlen($parts[1]) % 4))), true);
|
||||
|
||||
$this->assertSame('test-user', $payload['mercure']['payload']['username']);
|
||||
$this->assertSame('test-game', $payload['mercure']['payload']['gameAssoc']);
|
||||
$this->assertContains('*', $payload['mercure']['subscribe']);
|
||||
}
|
||||
}
|
||||
188
tests/Service/RecaptchaServiceTest.php
Normal file
188
tests/Service/RecaptchaServiceTest.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\Service\RecaptchaService;
|
||||
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* Class RecaptchaServiceTest
|
||||
*
|
||||
* @package App\Tests\Service
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 21.
|
||||
*/
|
||||
#[AllowMockObjectsWithoutExpectations]
|
||||
#[TestDox('Recaptcha Service')]
|
||||
class RecaptchaServiceTest extends TestCase
|
||||
{
|
||||
private const SECRET_KEY = 'test-secret-key';
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Verify returns false for empty token')]
|
||||
public function verifyReturnsFalseForEmptyToken(): void
|
||||
{
|
||||
$httpClient = $this->createMock(HttpClientInterface::class);
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);
|
||||
|
||||
$result = $service->verify('');
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Verify returns false when api returns failure')]
|
||||
public function verifyReturnsFalseWhenApiReturnsFailure(): void
|
||||
{
|
||||
$mockResponse = new MockResponse(json_encode([
|
||||
'success' => false,
|
||||
'error-codes' => ['invalid-input-secret'],
|
||||
], JSON_THROW_ON_ERROR));
|
||||
|
||||
$httpClient = new MockHttpClient($mockResponse);
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects($this->once())->method('info');
|
||||
|
||||
$service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);
|
||||
|
||||
$result = $service->verify('invalid-token');
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Verify returns true when api returns success and high score')]
|
||||
public function verifyReturnsTrueWhenApiReturnsSuccessAndHighScore(): void
|
||||
{
|
||||
$mockResponse = new MockResponse(json_encode([
|
||||
'success' => true,
|
||||
'score' => 0.8,
|
||||
'hostname' => 'test.com',
|
||||
], JSON_THROW_ON_ERROR));
|
||||
|
||||
$httpClient = new MockHttpClient($mockResponse);
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects($this->once())->method('info');
|
||||
|
||||
$service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);
|
||||
|
||||
$result = $service->verify('valid-token');
|
||||
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Verify returns false when score below threshold')]
|
||||
public function verifyReturnsFalseWhenScoreBelowThreshold(): void
|
||||
{
|
||||
$mockResponse = new MockResponse(json_encode([
|
||||
'success' => true,
|
||||
'score' => 0.3,
|
||||
'hostname' => 'test.com',
|
||||
], JSON_THROW_ON_ERROR));
|
||||
|
||||
$httpClient = new MockHttpClient($mockResponse);
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects($this->once())->method('info');
|
||||
|
||||
$service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);
|
||||
|
||||
$result = $service->verify('low-score-token');
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Verify returns false when api throws exception')]
|
||||
public function verifyReturnsFalseWhenApiThrowsException(): void
|
||||
{
|
||||
$mockResponse = new MockResponse('', ['http_code' => 500]);
|
||||
|
||||
$httpClient = new MockHttpClient($mockResponse);
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects($this->once())->method('error');
|
||||
|
||||
$service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);
|
||||
|
||||
$result = $service->verify('test-token');
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Verify includes remote ip when provided')]
|
||||
public function verifyIncludesRemoteIpWhenProvided(): void
|
||||
{
|
||||
$mockResponse = new MockResponse(json_encode([
|
||||
'success' => true,
|
||||
'score' => 0.9,
|
||||
], JSON_THROW_ON_ERROR));
|
||||
|
||||
$httpClient = new MockHttpClient($mockResponse);
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects($this->once())->method('info');
|
||||
|
||||
$service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);
|
||||
|
||||
$result = $service->verify('test-token', '192.168.1.1');
|
||||
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Verify with score at threshold')]
|
||||
public function verifyWithScoreAtThreshold(): void
|
||||
{
|
||||
$mockResponse = new MockResponse(json_encode([
|
||||
'success' => true,
|
||||
'score' => 0.5,
|
||||
], JSON_THROW_ON_ERROR));
|
||||
|
||||
$httpClient = new MockHttpClient($mockResponse);
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);
|
||||
|
||||
$result = $service->verify('threshold-token');
|
||||
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[TestDox('Verify with no score fails')]
|
||||
public function verifyWithNoScoreFails(): void
|
||||
{
|
||||
$mockResponse = new MockResponse(json_encode([
|
||||
'success' => true,
|
||||
], JSON_THROW_ON_ERROR));
|
||||
|
||||
$httpClient = new MockHttpClient($mockResponse);
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);
|
||||
|
||||
$result = $service->verify('no-score-token');
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
}
|
||||
21
tests/WebTestCase.php
Normal file
21
tests/WebTestCase.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
|
||||
use Zenstruck\Foundry\Test\Factories;
|
||||
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
|
||||
abstract class WebTestCase extends BaseWebTestCase
|
||||
{
|
||||
use ResetDatabase;
|
||||
use Factories;
|
||||
}
|
||||
21
tests/bootstrap.php
Normal file
21
tests/bootstrap.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2026 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
|
||||
if (method_exists(Dotenv::class, 'bootEnv')) {
|
||||
new Dotenv()->bootEnv(dirname(__DIR__).'/.env');
|
||||
}
|
||||
|
||||
if ($_SERVER['APP_DEBUG']) {
|
||||
umask(0000);
|
||||
}
|
||||
Reference in New Issue
Block a user