test: functional test infrastructure (ApiTestCase + db_test + JWT helpers)

- .env.test points at db_test (doctrine when@test appends _test suffix)
- ApiTestCase: WebTestCase base with createUser/jwtFor/authJson helpers
- InfraSmokeTest: proves boot + DB + JWT auth pipeline works (/health 200,
  unauth /oauth/userinfo 401, JWT-auth userinfo 200)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-28 16:44:05 +03:30
co-authored by Claude Opus 4.8
parent 05fde81320
commit 30c5fbe98c
3 changed files with 116 additions and 0 deletions
+8
View File
@@ -1,3 +1,11 @@
# define your env variables for the test env here
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
# Test DB: doctrine's when@test config appends the `_test` suffix (see
# config/packages/doctrine.yaml), so this base name `db` becomes `db_test`.
# That database is created in ddev and granted to user `db`.
DATABASE_URL="mysql://db:db@db:3306/db?serverVersion=8.0&charset=utf8mb4"
# JWT — generated keypair is shared with dev; passphrase from .env is fine.
# Redis cache/messenger use the same ddev redis; tests don't depend on it.
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace App\Tests;
use App\Auth\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* Base class for functional API tests.
*
* Provides a booted KernelBrowser, the EntityManager, and helpers to create
* users and issue JWTs so tests can hit authenticated /api and /oauth endpoints.
* Runs against the dedicated db_test database (see .env.test / doctrine when@test).
*/
abstract class ApiTestCase extends WebTestCase
{
protected KernelBrowser $client;
protected EntityManagerInterface $em;
protected function setUp(): void
{
$this->client = static::createClient();
$this->em = static::getContainer()->get(EntityManagerInterface::class);
}
/**
* Persist a user with the given roles. Mobile is randomised per test to
* avoid unique-constraint clashes across cases without a full DB reset.
*/
protected function createUser(array $roles = ['ROLE_USER'], ?string $mobile = null): User
{
$mobile ??= '0912' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT);
$user = new User($mobile);
$user->setRoles($roles);
$user->setStatus(1);
$this->em->persist($user);
$this->em->flush();
return $user;
}
protected function jwtFor(User $user): string
{
return static::getContainer()
->get(JWTTokenManagerInterface::class)
->create($user);
}
/**
* Issue an authenticated JSON request and return the decoded response body.
*/
protected function authJson(string $method, string $uri, User $user, array $body = []): array
{
$this->client->request(
$method,
$uri,
server: [
'HTTP_AUTHORIZATION' => 'Bearer ' . $this->jwtFor($user),
'CONTENT_TYPE' => 'application/json',
],
content: $body ? json_encode($body) : null,
);
return json_decode($this->client->getResponse()->getContent(), true) ?? [];
}
protected function responseCode(): int
{
return $this->client->getResponse()->getStatusCode();
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace App\Tests\Smoke;
use App\Tests\ApiTestCase;
/**
* Confirms the test infrastructure itself works: app boots, /health serves,
* and JWT-authenticated requests reach protected endpoints.
*/
class InfraSmokeTest extends ApiTestCase
{
public function testHealthIsPublicAnd200(): void
{
$this->client->request('GET', '/health');
$this->assertSame(200, $this->responseCode());
}
public function testUnauthenticatedApiIs401(): void
{
$this->client->request('GET', '/oauth/userinfo');
$this->assertSame(401, $this->responseCode());
}
public function testJwtAuthReachesUserInfo(): void
{
$user = $this->createUser(['ROLE_USER']);
$body = $this->authJson('GET', '/oauth/userinfo', $user);
$this->assertSame(200, $this->responseCode());
$this->assertTrue($body['success']);
$this->assertSame($user->getUuid(), $body['data']['uuid']);
}
}