Private
Public Access
1
0

new: pkg: add test cases to back-end w/ real database connection in it #10

This commit is contained in:
2026-04-21 17:56:04 +02:00
parent 6bf908b43e
commit d704be5bff
38 changed files with 5429 additions and 17 deletions

265
docs/testing/FACTORIES.md Normal file
View File

@@ -0,0 +1,265 @@
# Foundry Factories for MineSeeker
Factory classes for creating test data. For general Foundry usage, see [Zenstruck Foundry Docs](https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html).
---
## Available Factories
All factories are in `tests/Factory/`:
| Factory | Entity | Use For |
|---------|--------|---------|
| `UserFactory` | `User` | Registered users with auth |
| `GamerFactory` | `Gamer` | Anonymous/guest players |
| `PlayedGameFactory` | `PlayedGame` | Game records |
| `StepFactory` | `Step` | Individual game moves |
| `GridFactory` | `Grid` | 16×16 game grids |
| `GridRowFactory` | `GridRow` | Grid row data |
| `WebAuthnCredentialFactory` | `WebAuthnCredential` | Passkey credentials |
| `ContactMessageFactory` | `ContactMessage` | Contact form messages |
---
## Quick Examples
### UserFactory
```php
// Basic user
$user = UserFactory::createOne();
// Unverified user
$user = UserFactory::createOne(['isVerified' => false]);
// Admin user
$user = UserFactory::createOne(['roles' => ['ROLE_ADMIN']]);
// Multiple users
UserFactory::createMany(5);
```
### GamerFactory
```php
// Anonymous gamer
$gamer = GamerFactory::new()->anonymous()->create();
```
### PlayedGameFactory
```php
// Game with registered players
$game = PlayedGameFactory::new()
->withRegisteredPlayers()
->create();
// Game with anonymous players
$game = PlayedGameFactory::new()
->withAnonymousPlayers()
->create();
// Red wins
$game = PlayedGameFactory::new()
->withRegisteredPlayers()
->redWins()
->create();
// Blue wins
$game = PlayedGameFactory::new()
->withRegisteredPlayers()
->blueWins()
->create();
// Resigned game
$game = PlayedGameFactory::new()
->resigned('red')
->create();
```
### StepFactory
```php
// Mine hit
$step = StepFactory::new()
->forPlayer('red')
->mine()
->create(['playedGame' => $game]);
// Safe cell
$step = StepFactory::new()
->forPlayer('blue')
->safe()
->create(['playedGame' => $game]);
// With revealed cells
$step = StepFactory::new()
->withRevealedCells([
['row' => 5, 'col' => 5],
['row' => 5, 'col' => 6],
])
->create(['playedGame' => $game]);
```
### GridFactory
```php
// Full 16×16 grid
$grid = GridFactory::createOne(['playedGame' => $game]);
// Automatically creates 16 rows
self::assertCount(16, $grid->gridRow);
```
### WebAuthnCredentialFactory
```php
// Basic credential
$credential = WebAuthnCredentialFactory::createOne(['user' => $user]);
// Named credential, recently used
$credential = WebAuthnCredentialFactory::new()
->withName('YubiKey 5C')
->recentlyUsed()
->create(['user' => $user]);
```
### ContactMessageFactory
```php
// Basic message
$message = ContactMessageFactory::createOne();
// Without consent
$message = ContactMessageFactory::new()
->withoutConsent()
->create();
```
---
## Factory API Reference
### Common Methods (All Factories)
```php
// Create single entity
Factory::createOne([
'property' => 'value',
]);
// Create multiple
Factory::createMany(5);
// Create with factory methods
Factory::new()
->customMethod()
->create(['property' => 'value']);
// Access repository
Factory::repository()->findAll();
Factory::repository()->count([]);
Factory::repository()->findBy(['field' => 'value']);
```
### PlayedGameFactory Methods
| Method | Description |
|--------|-------------|
| `withRegisteredPlayers()` | Creates game with 2 registered users |
| `withAnonymousPlayers()` | Creates game with 2 anonymous gamers |
| `withMixedPlayers()` | One registered, one anonymous |
| `redWins()` | Red has 26 points |
| `blueWins()` | Blue has 26 points |
| `resigned(string $player)` | Set resignation ('red' or 'blue') |
### StepFactory Methods
| Method | Description |
|--------|-------------|
| `forPlayer(string $player)` | Set player ('red' or 'blue') |
| `mine()` | Step hits a mine |
| `safe()` | Step reveals safe cell |
| `withRevealedCells(array $cells)` | Set revealed cells array |
### GamerFactory Methods
| Method | Description |
|--------|-------------|
| `anonymous()` | Set username as "Guest_12345" |
### WebAuthnCredentialFactory Methods
| Method | Description |
|--------|-------------|
| `withName(string $name)` | Set credential name |
| `recentlyUsed()` | Set lastUsedAt to now |
### ContactMessageFactory Methods
| Method | Description |
|--------|-------------|
| `withoutConsent()` | Set consent to false |
| `anonymous()` | Remove IP address |
---
## MineSeeker-Specific Patterns
### Complete Game Setup
```php
// Create full game with steps and grid
$game = PlayedGameFactory::new()
->withRegisteredPlayers()
->create();
// Add steps
StepFactory::createMany(10, [
'playedGame' => $game,
'player' => 'red',
]);
// Add grid
$grid = GridFactory::createOne(['playedGame' => $game]);
```
### Testing Battle History
```php
// Create multiple finished games for a user
$user = UserFactory::createOne();
PlayedGameFactory::new()
->redWins()
->create(['red' => $user]);
PlayedGameFactory::new()
->blueWins()
->create(['blue' => $user]);
```
---
## Database Isolation
Tests automatically run in isolated transactions:
```php
public function testDatabaseIsolation(): void
{
UserFactory::createMany(5);
self::assertCount(5, UserFactory::repository()->findAll());
// Automatically rolled back after test
}
```
No manual cleanup needed. Each test starts with a clean database.
---
## External Resources
- **[Zenstruck Foundry Documentation](https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html)** - Complete Foundry guide
- **[DAMA Doctrine Test Bundle](https://github.com/dmaicher/doctrine-test-bundle)** - Transaction isolation details

317
docs/testing/TESTING.md Normal file
View File

@@ -0,0 +1,317 @@
# Testing Guide for MineSeeker
MineSeeker-specific testing setup and workflows. For general PHPUnit/Symfony testing, see [Symfony Testing Docs](https://symfony.com/doc/current/testing.html).
## Quick Start
```bash
# One-time setup
make test-db-setup
# Run tests
make test
```
---
## Test Database Configuration
MineSeeker uses a **separate test database** (`mineseeker_test`) with automatic transaction rollback for isolated tests.
### Stack
- **PHPUnit 13** - Testing framework
- **[Zenstruck Foundry](https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html)** - Factory library for test fixtures
- **[DAMA Doctrine Test Bundle](https://github.com/dmaicher/doctrine-test-bundle)** - Transaction isolation (rollback after each test)
### Configuration
**`phpunit.dist.xml`**:
```xml
<env name="DATABASE_URL" value="postgresql://...mineseeker_test..." />
<env name="DAMA_DISABLE_STATIC_CONNECTION" value="0" />
```
**`config/bundles.php`**:
```php
Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['dev' => true, 'test' => true],
DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true],
```
---
## Setup
### One-time Setup
```bash
make test-db-setup
```
Creates `mineseeker_test` database and runs migrations.
### Reset Test Database
```bash
make test-db-reset
```
Drops and recreates test database (useful after schema changes).
---
## Running Tests
```bash
# All tests
make test
# Specific file
vendor/bin/phpunit tests/Controller/ProfileControllerTest.php
# Specific method
vendor/bin/phpunit --filter testCreateUser
# With test names
vendor/bin/phpunit --testdox
```
---
## MineSeeker Factories
See **[FACTORIES.md](./FACTORIES.md)** for complete factory API.
### Available Factories
| Factory | Entity | Location |
|---------|--------|----------|
| `UserFactory` | Registered users | `tests/Factory/UserFactory.php` |
| `GamerFactory` | Anonymous players | `tests/Factory/GamerFactory.php` |
| `PlayedGameFactory` | Game records | `tests/Factory/PlayedGameFactory.php` |
| `StepFactory` | Game moves | `tests/Factory/StepFactory.php` |
| `GridFactory` | 16×16 game grids | `tests/Factory/GridFactory.php` |
| `GridRowFactory` | Grid rows | `tests/Factory/GridRowFactory.php` |
| `WebAuthnCredentialFactory` | Passkey credentials | `tests/Factory/WebAuthnCredentialFactory.php` |
| `ContactMessageFactory` | Contact messages | `tests/Factory/ContactMessageFactory.php` |
### Example
```php
use App\Tests\Factory\PlayedGameFactory;
use App\Tests\WebTestCase;
class GameTest extends WebTestCase
{
public function testGameCreation(): void
{
$game = PlayedGameFactory::new()
->withRegisteredPlayers()
->redWins()
->create();
self::assertEquals(26, $game->redPoints);
}
}
```
---
## Test Structure
All tests extend `App\Tests\WebTestCase`:
```php
<?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 App\Tests\WebTestCase;
class MyControllerTest extends WebTestCase
{
public function testExample(): void
{
$user = UserFactory::createOne();
$client = static::createClient();
$client->request('GET', '/profile');
self::assertResponseRedirects('/login');
}
}
```
**Important**: Always extend `App\Tests\WebTestCase`, not `Symfony\Bundle\FrameworkBundle\Test\WebTestCase`.
---
## Directory Structure
```
tests/
├── Controller/ # HTTP endpoint tests
├── Dto/ # Data Transfer Object tests
├── Entity/ # Entity logic tests
├── Service/ # Service layer tests
├── Integration/ # Integration tests
├── Factory/ # Foundry factories
├── WebTestCase.php # Base test class
└── bootstrap.php # PHPUnit bootstrap
```
---
## MineSeeker-Specific Patterns
### Testing Game Flow
```php
public function testRedPlayerWinsGame(): void
{
$game = PlayedGameFactory::new()
->withRegisteredPlayers()
->create();
// Create steps to simulate gameplay
for ($i = 0; $i < 26; $i++) {
StepFactory::new()
->forPlayer('red')
->mine()
->create(['playedGame' => $game]);
}
self::assertEquals(26, $game->redPoints);
}
```
### Testing Battle Sharing
```php
public function testBattleSharePageReturnsValidGame(): void
{
$game = PlayedGameFactory::new()
->withRegisteredPlayers()
->redWins()
->create();
$client = static::createClient();
$client->request('GET', '/battle/' . $game->uuid);
self::assertResponseIsSuccessful();
self::assertSelectorTextContains('h1', 'Battle Report');
}
```
### Testing Authentication
```php
public function testProfileRequiresAuthentication(): void
{
$client = static::createClient();
$client->request('GET', '/profile');
self::assertResponseRedirects('/login');
}
public function testAuthenticatedUserCanAccessProfile(): void
{
$user = UserFactory::createOne();
$client = static::createClient();
$client->loginUser($user->_real());
$client->request('GET', '/profile');
self::assertResponseIsSuccessful();
}
```
---
## Troubleshooting
### Tests interfering with each other?
Ensure you extend `App\Tests\WebTestCase`, not the base Symfony class.
### Schema out of sync?
```bash
make test-db-reset
```
### Memory limit errors?
```bash
php -d memory_limit=1024M vendor/bin/phpunit
```
Or increase in `phpunit.dist.xml`:
```xml
<ini name="memory_limit" value="1024M"/>
```
### Test database doesn't exist?
```bash
make test-db-setup
```
---
## External Resources
- **[Symfony Testing](https://symfony.com/doc/current/testing.html)** - Symfony testing guide
- **[Zenstruck Foundry](https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html)** - Factory documentation
- **[DAMA Doctrine Test Bundle](https://github.com/dmaicher/doctrine-test-bundle)** - Transaction isolation
- **[PHPUnit](https://docs.phpunit.de/)** - PHPUnit documentation
---
## Modern PHPUnit Attributes
MineSeeker tests use modern PHP 8 attributes instead of method name prefixes:
```php
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestDox;
#[TestDox('Security Controller')]
class SecurityControllerTest extends WebTestCase
{
#[Test]
#[TestDox('Login page loads successfully with form fields')]
public function loginPageLoadsSuccessfully(): void
{
// Test implementation
}
}
```
**Benefits:**
- ✅ More readable method names (no `test` prefix required)
- ✅ Self-documenting with `TestDox` descriptions
- ✅ Better IDE support and refactoring
- ✅ Cleaner `--testdox` output
**Run with documentation:**
```bash
vendor/bin/phpunit --testdox
```
Output:
```
Security Controller
✔ Login page loads successfully with form fields
✔ Login page has links to register and forgot password
✔ Register page loads successfully with form
```