Working state
This commit is contained in:
@@ -6,43 +6,43 @@ namespace Tests\Shared;
|
||||
use App\Shared\Bootstrap;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use ReflectionProperty;
|
||||
use Slim\Factory\AppFactory;
|
||||
use Slim\App;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||
|
||||
final class BootstrapTest extends TestCase
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private array $envBackup = [];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->envBackup = $_ENV;
|
||||
$this->envBackup = [
|
||||
'APP_AUTO_PROVISION' => $_ENV['APP_AUTO_PROVISION'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$_ENV = $this->envBackup;
|
||||
foreach ($this->envBackup as $key => $value) {
|
||||
if ($value === null) {
|
||||
unset($_ENV[$key]);
|
||||
} else {
|
||||
$_ENV[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetContainerReturnsPreloadedContainer(): void
|
||||
public function testInitializeInfrastructureReturnsPreloadedContainer(): void
|
||||
{
|
||||
$bootstrap = Bootstrap::create();
|
||||
$container = new class implements ContainerInterface {
|
||||
public function get(string $id): mixed
|
||||
{
|
||||
throw new \RuntimeException('Not expected');
|
||||
}
|
||||
$container = $this->createStub(ContainerInterface::class);
|
||||
|
||||
public function has(string $id): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
$this->setPrivate($bootstrap, 'container', $container);
|
||||
|
||||
$this->setPrivateProperty($bootstrap, 'container', $container);
|
||||
|
||||
self::assertSame($container, $bootstrap->getContainer());
|
||||
self::assertSame($container, $bootstrap->initializeInfrastructure());
|
||||
self::assertSame($container, $bootstrap->getContainer());
|
||||
}
|
||||
|
||||
public function testCreateHttpAppReturnsPreloadedApp(): void
|
||||
@@ -50,40 +50,29 @@ final class BootstrapTest extends TestCase
|
||||
$bootstrap = Bootstrap::create();
|
||||
$app = AppFactory::create();
|
||||
|
||||
$this->setPrivateProperty($bootstrap, 'app', $app);
|
||||
$this->setPrivate($bootstrap, 'app', $app);
|
||||
|
||||
self::assertSame($app, $bootstrap->createHttpApp());
|
||||
}
|
||||
|
||||
public function testInitializeReturnsPreloadedAppWhenAutoProvisioningDisabled(): void
|
||||
public function testInitializeReturnsPreloadedAppWhenAutoProvisionIsDisabled(): void
|
||||
{
|
||||
$_ENV['APP_ENV'] = 'production';
|
||||
$_ENV['APP_AUTO_PROVISION'] = '0';
|
||||
|
||||
$bootstrap = Bootstrap::create();
|
||||
$container = new class implements ContainerInterface {
|
||||
public function get(string $id): mixed
|
||||
{
|
||||
throw new \RuntimeException('Not expected');
|
||||
}
|
||||
|
||||
public function has(string $id): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
$container = $this->createStub(ContainerInterface::class);
|
||||
$app = AppFactory::create();
|
||||
|
||||
$this->setPrivateProperty($bootstrap, 'container', $container);
|
||||
$this->setPrivateProperty($bootstrap, 'app', $app);
|
||||
$this->setPrivate($bootstrap, 'container', $container);
|
||||
$this->setPrivate($bootstrap, 'app', $app);
|
||||
|
||||
self::assertSame($app, $bootstrap->initialize());
|
||||
}
|
||||
|
||||
private function setPrivateProperty(object $object, string $property, mixed $value): void
|
||||
private function setPrivate(Bootstrap $bootstrap, string $property, mixed $value): void
|
||||
{
|
||||
$reflection = new \ReflectionProperty($object, $property);
|
||||
$reflection = new ReflectionProperty($bootstrap, $property);
|
||||
$reflection->setAccessible(true);
|
||||
$reflection->setValue($object, $value);
|
||||
$reflection->setValue($bootstrap, $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use Slim\Psr7\Factory\ServerRequestFactory;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||
|
||||
final class ClientIpResolverTest extends TestCase
|
||||
{
|
||||
public function testResolveReturnsDefaultWhenRemoteAddrMissing(): void
|
||||
@@ -53,27 +54,4 @@ final class ClientIpResolverTest extends TestCase
|
||||
|
||||
self::assertSame('127.0.0.1', $resolver->resolve($request));
|
||||
}
|
||||
|
||||
public function testResolveReturnsRemoteAddrWhenTrustedProxyHasNoForwardedHeader(): void
|
||||
{
|
||||
$request = (new ServerRequestFactory())->createServerRequest('GET', '/', [
|
||||
'REMOTE_ADDR' => '127.0.0.1',
|
||||
]);
|
||||
|
||||
$resolver = new ClientIpResolver(['127.0.0.1']);
|
||||
|
||||
self::assertSame('127.0.0.1', $resolver->resolve($request));
|
||||
}
|
||||
|
||||
public function testResolveTrimsWhitespaceAroundRemoteAndForwardedAddresses(): void
|
||||
{
|
||||
$request = (new ServerRequestFactory())->createServerRequest('GET', '/', [
|
||||
'REMOTE_ADDR' => ' 127.0.0.1 ',
|
||||
'HTTP_X_FORWARDED_FOR' => ' 203.0.113.10 , 198.51.100.12',
|
||||
]);
|
||||
|
||||
$resolver = new ClientIpResolver(['*']);
|
||||
|
||||
self::assertSame('203.0.113.10', $resolver->resolve($request));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Shared\Config;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||
|
||||
final class ConfigTest extends TestCase
|
||||
{
|
||||
public function testGetTwigCacheReturnsFalseInDev(): void
|
||||
@@ -22,25 +23,6 @@ final class ConfigTest extends TestCase
|
||||
self::assertStringEndsWith('/var/cache/twig', $cachePath);
|
||||
}
|
||||
|
||||
public function testGetDatabasePathReturnsExistingFilePathUnchanged(): void
|
||||
{
|
||||
$dbFile = dirname(__DIR__, 2).'/database/app.sqlite';
|
||||
$dbDir = dirname($dbFile);
|
||||
|
||||
if (!is_dir($dbDir)) {
|
||||
mkdir($dbDir, 0755, true);
|
||||
}
|
||||
|
||||
if (!file_exists($dbFile)) {
|
||||
touch($dbFile);
|
||||
}
|
||||
|
||||
$path = Config::getDatabasePath();
|
||||
|
||||
self::assertSame($dbFile, $path);
|
||||
self::assertFileExists($dbFile);
|
||||
}
|
||||
|
||||
public function testGetDatabasePathCreatesDatabaseFileWhenMissing(): void
|
||||
{
|
||||
$dbFile = dirname(__DIR__, 2).'/database/app.sqlite';
|
||||
|
||||
@@ -46,21 +46,6 @@ final class ExtensionTest extends TestCase
|
||||
], $extension->getGlobals());
|
||||
}
|
||||
|
||||
public function testSessionExtensionExposesNullDefaultsWhenSessionIsEmpty(): void
|
||||
{
|
||||
$_SESSION = [];
|
||||
|
||||
$extension = new SessionExtension();
|
||||
|
||||
self::assertSame([
|
||||
'session' => [
|
||||
'user_id' => null,
|
||||
'username' => null,
|
||||
'role' => null,
|
||||
],
|
||||
], $extension->getGlobals());
|
||||
}
|
||||
|
||||
public function testCsrfExtensionExposesTokens(): void
|
||||
{
|
||||
$storage = [];
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Shared\Http\FlashService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||
|
||||
final class FlashServiceTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
@@ -34,26 +35,6 @@ final class FlashServiceTest extends TestCase
|
||||
self::assertArrayNotHasKey('count', $_SESSION['flash']);
|
||||
}
|
||||
|
||||
public function testGetCastsBooleanFalseToEmptyStringAndRemovesIt(): void
|
||||
{
|
||||
$_SESSION['flash']['flag'] = false;
|
||||
|
||||
$flash = new FlashService();
|
||||
|
||||
self::assertSame('', $flash->get('flag'));
|
||||
self::assertArrayNotHasKey('flag', $_SESSION['flash']);
|
||||
}
|
||||
|
||||
public function testSetOverridesPreviousMessageForSameKey(): void
|
||||
{
|
||||
$flash = new FlashService();
|
||||
|
||||
$flash->set('notice', 'Premier');
|
||||
$flash->set('notice', 'Second');
|
||||
|
||||
self::assertSame('Second', $flash->get('notice'));
|
||||
}
|
||||
|
||||
public function testGetReturnsNullWhenMissing(): void
|
||||
{
|
||||
$flash = new FlashService();
|
||||
|
||||
@@ -6,17 +6,17 @@ namespace Tests\Shared;
|
||||
use App\Shared\Mail\MailService;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionMethod;
|
||||
use Slim\Views\Twig;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||
|
||||
final class MailServiceTest extends TestCase
|
||||
{
|
||||
public function testCreateMailerUsesSslConfiguration(): void
|
||||
{
|
||||
$service = $this->createService('ssl', 465);
|
||||
|
||||
/** @var PHPMailer $mailer */
|
||||
$service = $this->makeService('ssl', 465);
|
||||
$mailer = $this->invokeCreateMailer($service);
|
||||
|
||||
self::assertSame('smtp', $mailer->Mailer);
|
||||
@@ -27,25 +27,23 @@ final class MailServiceTest extends TestCase
|
||||
self::assertSame(PHPMailer::ENCRYPTION_SMTPS, $mailer->SMTPSecure);
|
||||
self::assertSame(465, $mailer->Port);
|
||||
self::assertSame(PHPMailer::CHARSET_UTF8, $mailer->CharSet);
|
||||
self::assertSame('noreply@example.test', $mailer->From);
|
||||
self::assertSame('no-reply@example.test', $mailer->From);
|
||||
self::assertSame('Slim Blog', $mailer->FromName);
|
||||
}
|
||||
|
||||
public function testCreateMailerUsesStartTlsWhenEncryptionIsNotSsl(): void
|
||||
{
|
||||
$service = $this->createService('tls', 587);
|
||||
|
||||
/** @var PHPMailer $mailer */
|
||||
$service = $this->makeService('tls', 587);
|
||||
$mailer = $this->invokeCreateMailer($service);
|
||||
|
||||
self::assertSame(PHPMailer::ENCRYPTION_STARTTLS, $mailer->SMTPSecure);
|
||||
self::assertSame(587, $mailer->Port);
|
||||
}
|
||||
|
||||
private function createService(string $encryption, int $port): MailService
|
||||
private function makeService(string $encryption, int $port): MailService
|
||||
{
|
||||
$twig = new Twig(new ArrayLoader([
|
||||
'emails/test.twig' => '<p>Hello {{ name }}</p>',
|
||||
'emails/test.twig' => '<p>Bonjour {{ name }}</p>',
|
||||
]));
|
||||
|
||||
return new MailService(
|
||||
@@ -55,16 +53,19 @@ final class MailServiceTest extends TestCase
|
||||
'mailer-user',
|
||||
'mailer-pass',
|
||||
$encryption,
|
||||
'noreply@example.test',
|
||||
'no-reply@example.test',
|
||||
'Slim Blog',
|
||||
);
|
||||
}
|
||||
|
||||
private function invokeCreateMailer(MailService $service): mixed
|
||||
private function invokeCreateMailer(MailService $service): PHPMailer
|
||||
{
|
||||
$method = new \ReflectionMethod($service, 'createMailer');
|
||||
$method = new ReflectionMethod($service, 'createMailer');
|
||||
$method->setAccessible(true);
|
||||
|
||||
return $method->invoke($service);
|
||||
/** @var PHPMailer $mailer */
|
||||
$mailer = $method->invoke($service);
|
||||
|
||||
return $mailer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ namespace Tests\Shared;
|
||||
use App\Shared\Exception\NotFoundException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||
|
||||
final class NotFoundExceptionTest extends TestCase
|
||||
{
|
||||
public function testConstructorFormatsEntityAndIdentifierInMessage(): void
|
||||
public function testMessageContainsEntityAndIdentifier(): void
|
||||
{
|
||||
$exception = new NotFoundException('Article', 15);
|
||||
$exception = new NotFoundException('Article', 'mon-slug');
|
||||
|
||||
self::assertSame('Article introuvable : 15', $exception->getMessage());
|
||||
self::assertSame('Article introuvable : mon-slug', $exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,69 +7,70 @@ use App\Shared\Database\Provisioner;
|
||||
use PDO;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||
|
||||
final class ProvisionerTest extends TestCase
|
||||
{
|
||||
private PDO $db;
|
||||
private string $lockPath;
|
||||
private bool $lockExistedBefore;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $envBackup;
|
||||
private array $envBackup = [];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->db = new PDO('sqlite::memory:', options: [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
$this->db->sqliteCreateFunction('strip_tags', 'strip_tags', 1);
|
||||
|
||||
$this->lockPath = dirname(__DIR__, 2) . '/database/.provision.lock';
|
||||
$this->lockExistedBefore = file_exists($this->lockPath);
|
||||
@unlink($this->lockPath);
|
||||
|
||||
$this->envBackup = [
|
||||
'ADMIN_USERNAME' => $_ENV['ADMIN_USERNAME'] ?? '',
|
||||
'ADMIN_EMAIL' => $_ENV['ADMIN_EMAIL'] ?? '',
|
||||
'ADMIN_PASSWORD' => $_ENV['ADMIN_PASSWORD'] ?? '',
|
||||
'ADMIN_USERNAME' => $_ENV['ADMIN_USERNAME'] ?? null,
|
||||
'ADMIN_EMAIL' => $_ENV['ADMIN_EMAIL'] ?? null,
|
||||
'ADMIN_PASSWORD' => $_ENV['ADMIN_PASSWORD'] ?? null,
|
||||
];
|
||||
|
||||
$_ENV['ADMIN_USERNAME'] = 'shared-admin';
|
||||
$_ENV['ADMIN_EMAIL'] = 'shared-admin@example.com';
|
||||
$_ENV['ADMIN_PASSWORD'] = 'strong-secret';
|
||||
$_ENV['ADMIN_USERNAME'] = 'Admin';
|
||||
$_ENV['ADMIN_EMAIL'] = 'Admin@example.com';
|
||||
$_ENV['ADMIN_PASSWORD'] = 'secret1234';
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
foreach ($this->envBackup as $key => $value) {
|
||||
$_ENV[$key] = $value;
|
||||
}
|
||||
@unlink($this->lockPath);
|
||||
|
||||
if (!$this->lockExistedBefore && file_exists($this->lockPath)) {
|
||||
@unlink($this->lockPath);
|
||||
foreach ($this->envBackup as $key => $value) {
|
||||
if ($value === null) {
|
||||
unset($_ENV[$key]);
|
||||
} else {
|
||||
$_ENV[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testRunAppliesMigrationsSeedsAdminAndCreatesLockFile(): void
|
||||
public function testRunCreatesProvisionLockAndSeedsAdminUser(): void
|
||||
{
|
||||
Provisioner::run($this->db);
|
||||
|
||||
self::assertFileExists($this->lockPath);
|
||||
|
||||
$migrationCount = (int) $this->db->query('SELECT COUNT(*) FROM migrations')->fetchColumn();
|
||||
self::assertGreaterThan(0, $migrationCount, 'Les migrations doivent être enregistrées');
|
||||
$row = $this->db->query('SELECT username, email, role FROM users')->fetch();
|
||||
|
||||
$admin = $this->db->query("SELECT username, email, role FROM users WHERE username = 'shared-admin'")->fetch();
|
||||
self::assertIsArray($admin);
|
||||
self::assertSame('shared-admin@example.com', $admin['email']);
|
||||
self::assertSame('admin', $admin['role']);
|
||||
self::assertIsArray($row);
|
||||
self::assertSame('admin', $row['username']);
|
||||
self::assertSame('admin@example.com', $row['email']);
|
||||
self::assertSame('admin', $row['role']);
|
||||
}
|
||||
|
||||
public function testRunIsIdempotentForAdminSeed(): void
|
||||
public function testRunIsIdempotent(): void
|
||||
{
|
||||
Provisioner::run($this->db);
|
||||
Provisioner::run($this->db);
|
||||
|
||||
$adminCount = (int) $this->db->query("SELECT COUNT(*) FROM users WHERE username = 'shared-admin'")->fetchColumn();
|
||||
self::assertSame(1, $adminCount);
|
||||
$count = (int) $this->db->query('SELECT COUNT(*) FROM users WHERE username = "admin"')->fetchColumn();
|
||||
|
||||
self::assertSame(1, $count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ use App\Shared\Routes;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Slim\Factory\AppFactory;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||
|
||||
final class RoutesTest extends TestCase
|
||||
{
|
||||
public function testRegisterDeclaresExpectedPublicAndProtectedRoutes(): void
|
||||
@@ -15,46 +17,48 @@ final class RoutesTest extends TestCase
|
||||
Routes::register($app);
|
||||
|
||||
$actual = [];
|
||||
|
||||
foreach ($app->getRouteCollector()->getRoutes() as $route) {
|
||||
$pattern = $route->getPattern();
|
||||
$methods = $route->getMethods();
|
||||
|
||||
if (!isset($actual[$pattern])) {
|
||||
$actual[$pattern] = [];
|
||||
}
|
||||
$methods = array_values(array_diff($route->getMethods(), ['HEAD', 'OPTIONS']));
|
||||
|
||||
$actual[$pattern] ??= [];
|
||||
$actual[$pattern] = array_values(array_unique(array_merge($actual[$pattern], $methods)));
|
||||
sort($actual[$pattern]);
|
||||
}
|
||||
|
||||
ksort($actual);
|
||||
|
||||
$expected = [
|
||||
'/' => ['GET'],
|
||||
'/account/password' => ['GET', 'POST'],
|
||||
'/admin' => ['GET'],
|
||||
'/admin/categories' => ['GET'],
|
||||
'/admin/categories/create' => ['POST'],
|
||||
'/admin/categories/delete/{id}' => ['POST'],
|
||||
'/admin/media' => ['GET'],
|
||||
'/admin/media/delete/{id}' => ['POST'],
|
||||
'/admin/media/upload' => ['POST'],
|
||||
'/admin/posts' => ['GET'],
|
||||
'/admin/posts/create' => ['POST'],
|
||||
'/admin/posts/delete/{id}' => ['POST'],
|
||||
'/admin/posts/edit/{id}' => ['GET', 'POST'],
|
||||
'/admin/users' => ['GET'],
|
||||
'/admin/users/create' => ['GET', 'POST'],
|
||||
'/admin/users/delete/{id}' => ['POST'],
|
||||
'/admin/users/role/{id}' => ['POST'],
|
||||
'/article/{slug}' => ['GET'],
|
||||
'/rss.xml' => ['GET'],
|
||||
'/auth/login' => ['GET', 'POST'],
|
||||
'/auth/logout' => ['POST'],
|
||||
'/password/forgot' => ['GET', 'POST'],
|
||||
'/password/reset' => ['GET', 'POST'],
|
||||
'/account/password' => ['GET', 'POST'],
|
||||
'/admin' => ['GET'],
|
||||
'/admin/posts' => ['GET'],
|
||||
'/admin/posts/edit/{id}' => ['GET', 'POST'],
|
||||
'/admin/posts/create' => ['POST'],
|
||||
'/admin/posts/delete/{id}' => ['POST'],
|
||||
'/admin/media/upload' => ['POST'],
|
||||
'/admin/media' => ['GET'],
|
||||
'/admin/media/delete/{id}' => ['POST'],
|
||||
'/admin/categories' => ['GET'],
|
||||
'/admin/categories/create' => ['POST'],
|
||||
'/admin/categories/delete/{id}' => ['POST'],
|
||||
'/admin/users' => ['GET'],
|
||||
'/admin/users/create' => ['GET', 'POST'],
|
||||
'/admin/users/role/{id}' => ['POST'],
|
||||
'/admin/users/delete/{id}' => ['POST'],
|
||||
'/rss.xml' => ['GET'],
|
||||
];
|
||||
|
||||
foreach ($expected as $pattern => $methods) {
|
||||
sort($methods);
|
||||
}
|
||||
ksort($expected);
|
||||
ksort($actual);
|
||||
|
||||
self::assertSame($expected, $actual);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Shared\Http\SessionManager;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||
|
||||
final class SessionManagerEdgeCasesTest extends TestCase
|
||||
{
|
||||
private SessionManager $manager;
|
||||
@@ -30,14 +31,6 @@ final class SessionManagerEdgeCasesTest extends TestCase
|
||||
self::assertFalse($this->manager->isAuthenticated());
|
||||
}
|
||||
|
||||
public function testGetUserIdCastsNumericStringToInteger(): void
|
||||
{
|
||||
$_SESSION['user_id'] = '42';
|
||||
|
||||
self::assertSame(42, $this->manager->getUserId());
|
||||
self::assertTrue($this->manager->isAuthenticated());
|
||||
}
|
||||
|
||||
public function testSetUserUsesDefaultRoleUser(): void
|
||||
{
|
||||
$this->manager->setUser(12, 'julien');
|
||||
|
||||
Reference in New Issue
Block a user