Working state
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,7 +24,6 @@ public/assets/
|
|||||||
database/*.sqlite
|
database/*.sqlite
|
||||||
database/*.sqlite-shm
|
database/*.sqlite-shm
|
||||||
database/*.sqlite-wal
|
database/*.sqlite-wal
|
||||||
database/.provision.lock
|
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Cache & Logs
|
# Cache & Logs
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ final class MediaController
|
|||||||
public function upload(Request $req, Response $res): Response
|
public function upload(Request $req, Response $res): Response
|
||||||
{
|
{
|
||||||
$files = $req->getUploadedFiles();
|
$files = $req->getUploadedFiles();
|
||||||
$uploadedFile = $files['file'] ?? $files['image'] ?? null;
|
$uploadedFile = $files['image'] ?? null;
|
||||||
|
|
||||||
if ($uploadedFile === null || $uploadedFile->getError() !== UPLOAD_ERR_OK) {
|
if ($uploadedFile === null || $uploadedFile->getError() !== UPLOAD_ERR_OK) {
|
||||||
return $this->jsonError($res, "Aucun fichier reçu ou erreur d'upload", 400);
|
return $this->jsonError($res, "Aucun fichier reçu ou erreur d'upload", 400);
|
||||||
@@ -103,10 +103,7 @@ final class MediaController
|
|||||||
return $this->jsonError($res, $e->getMessage(), 500);
|
return $this->jsonError($res, $e->getMessage(), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->jsonOk($res, [
|
return $this->jsonSuccess($res, $url);
|
||||||
'url' => $url,
|
|
||||||
'file' => $url,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -148,21 +145,21 @@ final class MediaController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retourne une réponse JSON de succès.
|
* Retourne une réponse JSON de succès avec l'URL du fichier uploadé.
|
||||||
*
|
*
|
||||||
* @param Response $res La réponse HTTP
|
* @param Response $res La réponse HTTP
|
||||||
* @param array<string, mixed> $data Données supplémentaires à fusionner
|
* @param string $fileUrl L'URL publique du fichier
|
||||||
*
|
*
|
||||||
* @return Response La réponse JSON {"success": true, ...}
|
* @return Response La réponse JSON {"success": true, "file": "..."}
|
||||||
*/
|
*/
|
||||||
private function jsonOk(Response $res, array $data = []): Response
|
private function jsonSuccess(Response $res, string $fileUrl): Response
|
||||||
{
|
{
|
||||||
$payload = json_encode(array_merge(['success' => true], $data), JSON_THROW_ON_ERROR);
|
$res->getBody()->write(json_encode([
|
||||||
$res->getBody()->write($payload);
|
'success' => true,
|
||||||
|
'file' => $fileUrl,
|
||||||
|
], JSON_THROW_ON_ERROR));
|
||||||
|
|
||||||
return $res
|
return $res->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||||
->withHeader('Content-Type', 'application/json')
|
|
||||||
->withStatus(200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ final class PasswordResetRepositoryTest extends TestCase
|
|||||||
public function testCreateSetsCreatedAt(): void
|
public function testCreateSetsCreatedAt(): void
|
||||||
{
|
{
|
||||||
$stmt = $this->stmtOk();
|
$stmt = $this->stmtOk();
|
||||||
$this->db->method('prepare')->willReturn($stmt);
|
$this->db->expects($this->once())->method('prepare')->willReturn($stmt);
|
||||||
|
|
||||||
$stmt->expects($this->once())
|
$stmt->expects($this->once())
|
||||||
->method('execute')
|
->method('execute')
|
||||||
@@ -104,7 +104,7 @@ final class PasswordResetRepositoryTest extends TestCase
|
|||||||
public function testFindActiveByHashReturnsNullWhenMissing(): void
|
public function testFindActiveByHashReturnsNullWhenMissing(): void
|
||||||
{
|
{
|
||||||
$stmt = $this->stmtOk(false);
|
$stmt = $this->stmtOk(false);
|
||||||
$this->db->method('prepare')->willReturn($stmt);
|
$this->db->expects($this->once())->method('prepare')->willReturn($stmt);
|
||||||
|
|
||||||
$this->assertNull($this->repository->findActiveByHash('hashquinaexistepas'));
|
$this->assertNull($this->repository->findActiveByHash('hashquinaexistepas'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,8 @@ use PDO;
|
|||||||
use PHPUnit\Framework\MockObject\MockObject;
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests unitaires pour PasswordResetService.
|
|
||||||
*
|
|
||||||
* Vérifie la génération de token, la validation et la réinitialisation
|
|
||||||
* du mot de passe. Les dépendances sont mockées via leurs interfaces.
|
|
||||||
*/
|
|
||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
|
|
||||||
final class PasswordResetServiceTest extends TestCase
|
final class PasswordResetServiceTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @var PasswordResetRepositoryInterface&MockObject */
|
/** @var PasswordResetRepositoryInterface&MockObject */
|
||||||
@@ -52,13 +47,6 @@ final class PasswordResetServiceTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ── requestReset ───────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* requestReset() avec un email inconnu ne doit ni envoyer d'email
|
|
||||||
* ni lever d'exception (protection contre l'énumération d'emails).
|
|
||||||
*/
|
|
||||||
public function testRequestResetUnknownEmailReturnsSilently(): void
|
public function testRequestResetUnknownEmailReturnsSilently(): void
|
||||||
{
|
{
|
||||||
$this->userRepository->method('findByEmail')->willReturn(null);
|
$this->userRepository->method('findByEmail')->willReturn(null);
|
||||||
@@ -68,9 +56,6 @@ final class PasswordResetServiceTest extends TestCase
|
|||||||
$this->service->requestReset('inconnu@example.com', 'https://blog.exemple.com');
|
$this->service->requestReset('inconnu@example.com', 'https://blog.exemple.com');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* requestReset() doit invalider les tokens précédents avant d'en créer un nouveau.
|
|
||||||
*/
|
|
||||||
public function testRequestResetInvalidatesPreviousTokens(): void
|
public function testRequestResetInvalidatesPreviousTokens(): void
|
||||||
{
|
{
|
||||||
$user = $this->makeUser();
|
$user = $this->makeUser();
|
||||||
@@ -79,39 +64,39 @@ final class PasswordResetServiceTest extends TestCase
|
|||||||
$this->resetRepository->expects($this->once())
|
$this->resetRepository->expects($this->once())
|
||||||
->method('invalidateByUserId')
|
->method('invalidateByUserId')
|
||||||
->with($user->getId());
|
->with($user->getId());
|
||||||
$this->resetRepository->method('create');
|
$this->resetRepository->expects($this->once())
|
||||||
$this->mailService->method('send');
|
->method('create');
|
||||||
|
$this->mailService->expects($this->once())
|
||||||
|
->method('send');
|
||||||
|
|
||||||
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* requestReset() doit persister un nouveau token en base.
|
|
||||||
*/
|
|
||||||
public function testRequestResetCreatesTokenInDatabase(): void
|
public function testRequestResetCreatesTokenInDatabase(): void
|
||||||
{
|
{
|
||||||
$user = $this->makeUser();
|
$user = $this->makeUser();
|
||||||
|
|
||||||
$this->userRepository->method('findByEmail')->willReturn($user);
|
$this->userRepository->method('findByEmail')->willReturn($user);
|
||||||
$this->resetRepository->method('invalidateByUserId');
|
$this->resetRepository->expects($this->once())
|
||||||
|
->method('invalidateByUserId');
|
||||||
$this->resetRepository->expects($this->once())
|
$this->resetRepository->expects($this->once())
|
||||||
->method('create')
|
->method('create')
|
||||||
->with($user->getId(), $this->callback('is_string'), $this->callback('is_string'));
|
->with($user->getId(), $this->callback('is_string'), $this->callback('is_string'));
|
||||||
$this->mailService->method('send');
|
$this->mailService->expects($this->once())
|
||||||
|
->method('send');
|
||||||
|
|
||||||
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* requestReset() doit envoyer un email avec le bon destinataire et template.
|
|
||||||
*/
|
|
||||||
public function testRequestResetSendsEmailWithCorrectAddress(): void
|
public function testRequestResetSendsEmailWithCorrectAddress(): void
|
||||||
{
|
{
|
||||||
$user = $this->makeUser();
|
$user = $this->makeUser();
|
||||||
|
|
||||||
$this->userRepository->method('findByEmail')->willReturn($user);
|
$this->userRepository->method('findByEmail')->willReturn($user);
|
||||||
$this->resetRepository->method('invalidateByUserId');
|
$this->resetRepository->expects($this->once())
|
||||||
$this->resetRepository->method('create');
|
->method('invalidateByUserId');
|
||||||
|
$this->resetRepository->expects($this->once())
|
||||||
|
->method('create');
|
||||||
|
|
||||||
$this->mailService->expects($this->once())
|
$this->mailService->expects($this->once())
|
||||||
->method('send')
|
->method('send')
|
||||||
@@ -125,16 +110,15 @@ final class PasswordResetServiceTest extends TestCase
|
|||||||
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* L'URL de réinitialisation dans le contexte de l'email doit contenir le token brut.
|
|
||||||
*/
|
|
||||||
public function testRequestResetUrlContainsToken(): void
|
public function testRequestResetUrlContainsToken(): void
|
||||||
{
|
{
|
||||||
$user = $this->makeUser();
|
$user = $this->makeUser();
|
||||||
|
|
||||||
$this->userRepository->method('findByEmail')->willReturn($user);
|
$this->userRepository->method('findByEmail')->willReturn($user);
|
||||||
$this->resetRepository->method('invalidateByUserId');
|
$this->resetRepository->expects($this->once())
|
||||||
$this->resetRepository->method('create');
|
->method('invalidateByUserId');
|
||||||
|
$this->resetRepository->expects($this->once())
|
||||||
|
->method('create');
|
||||||
|
|
||||||
$this->mailService->expects($this->once())
|
$this->mailService->expects($this->once())
|
||||||
->method('send')
|
->method('send')
|
||||||
@@ -151,30 +135,26 @@ final class PasswordResetServiceTest extends TestCase
|
|||||||
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
$this->service->requestReset('alice@example.com', 'https://blog.exemple.com');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ── validateToken ──────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* validateToken() avec un token inexistant doit retourner null.
|
|
||||||
*/
|
|
||||||
public function testValidateTokenMissingToken(): void
|
public function testValidateTokenMissingToken(): void
|
||||||
{
|
{
|
||||||
$this->resetRepository->method('findActiveByHash')->willReturn(null);
|
$this->resetRepository->expects($this->once())
|
||||||
|
->method('findActiveByHash')
|
||||||
|
->willReturn(null);
|
||||||
|
|
||||||
$result = $this->service->validateToken('tokeninexistant');
|
$result = $this->service->validateToken('tokeninexistant');
|
||||||
|
|
||||||
$this->assertNull($result);
|
$this->assertNull($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* validateToken() avec un token expiré doit retourner null.
|
|
||||||
*/
|
|
||||||
public function testValidateTokenExpiredToken(): void
|
public function testValidateTokenExpiredToken(): void
|
||||||
{
|
{
|
||||||
$tokenRaw = 'montokenbrut';
|
$tokenRaw = 'montokenbrut';
|
||||||
$tokenHash = hash('sha256', $tokenRaw);
|
$tokenHash = hash('sha256', $tokenRaw);
|
||||||
|
|
||||||
$this->resetRepository->expects($this->once())->method('findActiveByHash')->with($tokenHash)->willReturn([
|
$this->resetRepository->expects($this->once())
|
||||||
|
->method('findActiveByHash')
|
||||||
|
->with($tokenHash)
|
||||||
|
->willReturn([
|
||||||
'user_id' => 1,
|
'user_id' => 1,
|
||||||
'token_hash' => $tokenHash,
|
'token_hash' => $tokenHash,
|
||||||
'expires_at' => date('Y-m-d H:i:s', time() - 3600),
|
'expires_at' => date('Y-m-d H:i:s', time() - 3600),
|
||||||
@@ -186,49 +166,50 @@ final class PasswordResetServiceTest extends TestCase
|
|||||||
$this->assertNull($result);
|
$this->assertNull($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* validateToken() avec un token valide doit retourner l'utilisateur associé.
|
|
||||||
*/
|
|
||||||
public function testValidateTokenValidToken(): void
|
public function testValidateTokenValidToken(): void
|
||||||
{
|
{
|
||||||
$user = $this->makeUser();
|
$user = $this->makeUser();
|
||||||
$tokenRaw = 'montokenbrut';
|
$tokenRaw = 'montokenbrut';
|
||||||
$tokenHash = hash('sha256', $tokenRaw);
|
$tokenHash = hash('sha256', $tokenRaw);
|
||||||
|
|
||||||
$this->resetRepository->expects($this->once())->method('findActiveByHash')->with($tokenHash)->willReturn([
|
$this->resetRepository->expects($this->once())
|
||||||
|
->method('findActiveByHash')
|
||||||
|
->with($tokenHash)
|
||||||
|
->willReturn([
|
||||||
'user_id' => $user->getId(),
|
'user_id' => $user->getId(),
|
||||||
'token_hash' => $tokenHash,
|
'token_hash' => $tokenHash,
|
||||||
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
||||||
'used_at' => null,
|
'used_at' => null,
|
||||||
]);
|
]);
|
||||||
$this->userRepository->expects($this->once())->method('findById')->with($user->getId())->willReturn($user);
|
$this->userRepository->expects($this->once())
|
||||||
|
->method('findById')
|
||||||
|
->with($user->getId())
|
||||||
|
->willReturn($user);
|
||||||
|
|
||||||
$result = $this->service->validateToken($tokenRaw);
|
$result = $this->service->validateToken($tokenRaw);
|
||||||
|
|
||||||
$this->assertSame($user, $result);
|
$this->assertSame($user, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ── resetPassword ──────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* resetPassword() avec un token invalide doit lever une InvalidArgumentException.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* validateToken() doit retourner null si le token est valide mais l'utilisateur a été supprimé.
|
|
||||||
*/
|
|
||||||
public function testValidateTokenDeletedUserReturnsNull(): void
|
public function testValidateTokenDeletedUserReturnsNull(): void
|
||||||
{
|
{
|
||||||
$row = [
|
$tokenRaw = 'token-valide-mais-user-supprime';
|
||||||
|
$tokenHash = hash('sha256', $tokenRaw);
|
||||||
|
|
||||||
|
$this->resetRepository->expects($this->once())
|
||||||
|
->method('findActiveByHash')
|
||||||
|
->with($tokenHash)
|
||||||
|
->willReturn([
|
||||||
'user_id' => 999,
|
'user_id' => 999,
|
||||||
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
||||||
'used_at' => null,
|
'used_at' => null,
|
||||||
];
|
]);
|
||||||
|
$this->userRepository->expects($this->once())
|
||||||
|
->method('findById')
|
||||||
|
->with(999)
|
||||||
|
->willReturn(null);
|
||||||
|
|
||||||
$this->resetRepository->expects($this->once())->method('findActiveByHash')->willReturn($row);
|
$result = $this->service->validateToken($tokenRaw);
|
||||||
$this->userRepository->expects($this->once())->method('findById')->with(999)->willReturn(null);
|
|
||||||
|
|
||||||
$result = $this->service->validateToken('token-valide-mais-user-supprime');
|
|
||||||
|
|
||||||
$this->assertNull($result);
|
$this->assertNull($result);
|
||||||
}
|
}
|
||||||
@@ -238,7 +219,7 @@ final class PasswordResetServiceTest extends TestCase
|
|||||||
$this->db->method('beginTransaction')->willReturn(true);
|
$this->db->method('beginTransaction')->willReturn(true);
|
||||||
$this->db->method('inTransaction')->willReturn(true);
|
$this->db->method('inTransaction')->willReturn(true);
|
||||||
$this->db->expects($this->once())->method('rollBack');
|
$this->db->expects($this->once())->method('rollBack');
|
||||||
$this->resetRepository->method('consumeActiveToken')->willReturn(null);
|
$this->resetRepository->expects($this->once())->method('consumeActiveToken')->willReturn(null);
|
||||||
|
|
||||||
$this->expectException(InvalidResetTokenException::class);
|
$this->expectException(InvalidResetTokenException::class);
|
||||||
$this->expectExceptionMessageMatches('/invalide ou a expiré/');
|
$this->expectExceptionMessageMatches('/invalide ou a expiré/');
|
||||||
@@ -246,31 +227,13 @@ final class PasswordResetServiceTest extends TestCase
|
|||||||
$this->service->resetPassword('tokeninvalide', 'nouveaumdp1');
|
$this->service->resetPassword('tokeninvalide', 'nouveaumdp1');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* resetPassword() avec un mot de passe trop court doit lever WeakPasswordException.
|
|
||||||
*/
|
|
||||||
public function testResetPasswordTooShortPasswordThrowsWeakPasswordException(): void
|
public function testResetPasswordTooShortPasswordThrowsWeakPasswordException(): void
|
||||||
{
|
{
|
||||||
$user = $this->makeUser();
|
|
||||||
$tokenRaw = 'montokenbrut';
|
|
||||||
$tokenHash = hash('sha256', $tokenRaw);
|
|
||||||
|
|
||||||
$this->resetRepository->method('findActiveByHash')->willReturn([
|
|
||||||
'user_id' => $user->getId(),
|
|
||||||
'token_hash' => $tokenHash,
|
|
||||||
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
|
||||||
'used_at' => null,
|
|
||||||
]);
|
|
||||||
$this->userRepository->method('findById')->willReturn($user);
|
|
||||||
|
|
||||||
$this->expectException(WeakPasswordException::class);
|
$this->expectException(WeakPasswordException::class);
|
||||||
|
|
||||||
$this->service->resetPassword($tokenRaw, '1234567');
|
$this->service->resetPassword('montokenbrut', '1234567');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* resetPassword() doit mettre à jour le mot de passe et marquer le token comme consommé.
|
|
||||||
*/
|
|
||||||
public function testResetPasswordUpdatesPasswordAndConsumesToken(): void
|
public function testResetPasswordUpdatesPasswordAndConsumesToken(): void
|
||||||
{
|
{
|
||||||
$user = $this->makeUser();
|
$user = $this->makeUser();
|
||||||
@@ -290,7 +253,10 @@ final class PasswordResetServiceTest extends TestCase
|
|||||||
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
||||||
'used_at' => null,
|
'used_at' => null,
|
||||||
]);
|
]);
|
||||||
$this->userRepository->method('findById')->willReturn($user);
|
$this->userRepository->expects($this->once())
|
||||||
|
->method('findById')
|
||||||
|
->with($user->getId())
|
||||||
|
->willReturn($user);
|
||||||
|
|
||||||
$this->userRepository->expects($this->once())
|
$this->userRepository->expects($this->once())
|
||||||
->method('updatePassword')
|
->method('updatePassword')
|
||||||
@@ -299,12 +265,6 @@ final class PasswordResetServiceTest extends TestCase
|
|||||||
$this->service->resetPassword($tokenRaw, 'nouveaumdp1');
|
$this->service->resetPassword($tokenRaw, 'nouveaumdp1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ── Helpers ────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Crée un utilisateur de test standard.
|
|
||||||
*/
|
|
||||||
private function makeUser(): User
|
private function makeUser(): User
|
||||||
{
|
{
|
||||||
return new User(
|
return new User(
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ final class CategoryRepositoryTest extends TestCase
|
|||||||
public function testFindByIdReturnsNullWhenMissing(): void
|
public function testFindByIdReturnsNullWhenMissing(): void
|
||||||
{
|
{
|
||||||
$stmt = $this->stmtForRead(row: false);
|
$stmt = $this->stmtForRead(row: false);
|
||||||
$this->db->method('prepare')->willReturn($stmt);
|
$this->db->expects($this->once())->method('prepare')->willReturn($stmt);
|
||||||
|
|
||||||
$this->assertNull($this->repository->findById(99));
|
$this->assertNull($this->repository->findById(99));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,35 +202,7 @@ final class MediaControllerTest extends ControllerTestCase
|
|||||||
|
|
||||||
$this->assertStatus($res, 200);
|
$this->assertStatus($res, 200);
|
||||||
$this->assertJsonContentType($res);
|
$this->assertJsonContentType($res);
|
||||||
$this->assertJsonContains($res, [
|
$this->assertJsonContains($res, ['success' => true, 'file' => '/media/abc123.webp']);
|
||||||
'success' => true,
|
|
||||||
'url' => '/media/abc123.webp',
|
|
||||||
'file' => '/media/abc123.webp',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* upload() doit utiliser 0 comme identifiant utilisateur de secours si la session ne contient pas d'utilisateur.
|
|
||||||
*/
|
|
||||||
public function testUploadUsesZeroAsFallbackUserId(): void
|
|
||||||
{
|
|
||||||
$file = $this->makeValidUploadedFile();
|
|
||||||
$this->sessionManager->method('getUserId')->willReturn(null);
|
|
||||||
|
|
||||||
$this->mediaService->expects($this->once())
|
|
||||||
->method('store')
|
|
||||||
->with($file, 0)
|
|
||||||
->willReturn('/media/fallback-user.webp');
|
|
||||||
|
|
||||||
$req = $this->makePost('/admin/media/upload')->withUploadedFiles(['image' => $file]);
|
|
||||||
$res = $this->controller->upload($req, $this->makeResponse());
|
|
||||||
|
|
||||||
$this->assertStatus($res, 200);
|
|
||||||
$this->assertJsonContains($res, [
|
|
||||||
'success' => true,
|
|
||||||
'url' => '/media/fallback-user.webp',
|
|
||||||
'file' => '/media/fallback-user.webp',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── delete ───────────────────────────────────────────────────────
|
// ── delete ───────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Tests\Media;
|
|
||||||
|
|
||||||
use App\Media\MediaController;
|
|
||||||
use App\Media\MediaServiceInterface;
|
|
||||||
use App\Shared\Http\FlashServiceInterface;
|
|
||||||
use App\Shared\Http\SessionManagerInterface;
|
|
||||||
use PHPUnit\Framework\MockObject\MockObject;
|
|
||||||
use Psr\Http\Message\UploadedFileInterface;
|
|
||||||
use Tests\ControllerTestCase;
|
|
||||||
|
|
||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
|
||||||
final class MediaControllerUploadCompatibilityTest extends ControllerTestCase
|
|
||||||
{
|
|
||||||
/** @var \Slim\Views\Twig&MockObject */
|
|
||||||
private \Slim\Views\Twig $view;
|
|
||||||
|
|
||||||
/** @var MediaServiceInterface&MockObject */
|
|
||||||
private MediaServiceInterface $mediaService;
|
|
||||||
|
|
||||||
/** @var FlashServiceInterface&MockObject */
|
|
||||||
private FlashServiceInterface $flash;
|
|
||||||
|
|
||||||
/** @var SessionManagerInterface&MockObject */
|
|
||||||
private SessionManagerInterface $sessionManager;
|
|
||||||
|
|
||||||
private MediaController $controller;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
$this->view = $this->makeTwigMock();
|
|
||||||
$this->mediaService = $this->createMock(MediaServiceInterface::class);
|
|
||||||
$this->flash = $this->createMock(FlashServiceInterface::class);
|
|
||||||
$this->sessionManager = $this->createMock(SessionManagerInterface::class);
|
|
||||||
|
|
||||||
$this->controller = new MediaController(
|
|
||||||
$this->view,
|
|
||||||
$this->mediaService,
|
|
||||||
$this->flash,
|
|
||||||
$this->sessionManager,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testUploadAcceptsFileFieldNameUsedByTrumbowyg(): void
|
|
||||||
{
|
|
||||||
$file = $this->makeValidUploadedFile();
|
|
||||||
$this->sessionManager->method('getUserId')->willReturn(7);
|
|
||||||
$this->mediaService->expects($this->once())
|
|
||||||
->method('store')
|
|
||||||
->with($file, 7)
|
|
||||||
->willReturn('/media/from-file-field.webp');
|
|
||||||
|
|
||||||
$req = $this->makePost('/admin/media/upload')->withUploadedFiles(['file' => $file]);
|
|
||||||
$res = $this->controller->upload($req, $this->makeResponse());
|
|
||||||
|
|
||||||
$this->assertStatus($res, 200);
|
|
||||||
$this->assertJsonContains($res, [
|
|
||||||
'success' => true,
|
|
||||||
'url' => '/media/from-file-field.webp',
|
|
||||||
'file' => '/media/from-file-field.webp',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function testUploadPrefersFileFieldWhenBothFileAndImageArePresent(): void
|
|
||||||
{
|
|
||||||
$fileField = $this->makeValidUploadedFile();
|
|
||||||
$imageField = $this->makeValidUploadedFile();
|
|
||||||
|
|
||||||
$this->sessionManager->method('getUserId')->willReturn(11);
|
|
||||||
$this->mediaService->expects($this->once())
|
|
||||||
->method('store')
|
|
||||||
->with($fileField, 11)
|
|
||||||
->willReturn('/media/preferred-file-field.webp');
|
|
||||||
|
|
||||||
$req = $this->makePost('/admin/media/upload')->withUploadedFiles([
|
|
||||||
'file' => $fileField,
|
|
||||||
'image' => $imageField,
|
|
||||||
]);
|
|
||||||
$res = $this->controller->upload($req, $this->makeResponse());
|
|
||||||
|
|
||||||
$this->assertStatus($res, 200);
|
|
||||||
$this->assertJsonContains($res, [
|
|
||||||
'success' => true,
|
|
||||||
'url' => '/media/preferred-file-field.webp',
|
|
||||||
'file' => '/media/preferred-file-field.webp',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testUploadSuccessResponseContainsBothUrlAndFileKeys(): void
|
|
||||||
{
|
|
||||||
$file = $this->makeValidUploadedFile();
|
|
||||||
$this->sessionManager->method('getUserId')->willReturn(3);
|
|
||||||
$this->mediaService->method('store')->willReturn('/media/dual-key.webp');
|
|
||||||
|
|
||||||
$req = $this->makePost('/admin/media/upload')->withUploadedFiles(['image' => $file]);
|
|
||||||
$res = $this->controller->upload($req, $this->makeResponse());
|
|
||||||
|
|
||||||
$this->assertStatus($res, 200);
|
|
||||||
$this->assertJsonContains($res, [
|
|
||||||
'success' => true,
|
|
||||||
'url' => '/media/dual-key.webp',
|
|
||||||
'file' => '/media/dual-key.webp',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return UploadedFileInterface&MockObject */
|
|
||||||
private function makeValidUploadedFile(): UploadedFileInterface
|
|
||||||
{
|
|
||||||
$file = $this->createMock(UploadedFileInterface::class);
|
|
||||||
$file->method('getError')->willReturn(UPLOAD_ERR_OK);
|
|
||||||
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -127,7 +127,7 @@ final class MediaRepositoryTest extends TestCase
|
|||||||
public function testFindByUserIdReturnsEmptyArrayWhenNone(): void
|
public function testFindByUserIdReturnsEmptyArrayWhenNone(): void
|
||||||
{
|
{
|
||||||
$stmt = $this->stmtForRead([]);
|
$stmt = $this->stmtForRead([]);
|
||||||
$this->db->method('prepare')->willReturn($stmt);
|
$this->db->expects($this->once())->method('prepare')->willReturn($stmt);
|
||||||
|
|
||||||
$this->assertSame([], $this->repository->findByUserId(99));
|
$this->assertSame([], $this->repository->findByUserId(99));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ final class MediaServiceTest extends TestCase
|
|||||||
private function makeUploadedFile(int $size): UploadedFileInterface
|
private function makeUploadedFile(int $size): UploadedFileInterface
|
||||||
{
|
{
|
||||||
$stream = $this->createMock(StreamInterface::class);
|
$stream = $this->createMock(StreamInterface::class);
|
||||||
$stream->method('getMetadata')->willReturnMap([['uri', '/nonexistent/path']]);
|
$stream->method('getMetadata')->willReturnCallback(static fn (?string $key = null): mixed => $key === 'uri' ? '/nonexistent/path' : null);
|
||||||
|
|
||||||
$file = $this->createMock(UploadedFileInterface::class);
|
$file = $this->createMock(UploadedFileInterface::class);
|
||||||
$file->method('getSize')->willReturn($size);
|
$file->method('getSize')->willReturn($size);
|
||||||
@@ -227,7 +227,7 @@ final class MediaServiceTest extends TestCase
|
|||||||
private function makeUploadedFileFromPath(string $path, int $size): UploadedFileInterface
|
private function makeUploadedFileFromPath(string $path, int $size): UploadedFileInterface
|
||||||
{
|
{
|
||||||
$stream = $this->createMock(StreamInterface::class);
|
$stream = $this->createMock(StreamInterface::class);
|
||||||
$stream->method('getMetadata')->willReturnMap([['uri', $path]]);
|
$stream->expects($this->once())->method('getMetadata')->with('uri')->willReturn($path);
|
||||||
|
|
||||||
$file = $this->createMock(UploadedFileInterface::class);
|
$file = $this->createMock(UploadedFileInterface::class);
|
||||||
$file->method('getSize')->willReturn($size);
|
$file->method('getSize')->willReturn($size);
|
||||||
|
|||||||
@@ -59,21 +59,6 @@ final class PostExtensionTest extends TestCase
|
|||||||
self::assertNull($this->call('post_thumbnail', $post));
|
self::assertNull($this->call('post_thumbnail', $post));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function testPostExcerptReturnsHtmlUnchangedWhenContentIsShortEnough(): void
|
|
||||||
{
|
|
||||||
$post = new Post(4, 'Titre', '<p><strong>Bonjour</strong> monde</p>', 'titre-4');
|
|
||||||
|
|
||||||
self::assertSame('<strong>Bonjour</strong> monde', $this->call('post_excerpt', $post, 50));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testPostInitialsFallsBackToFirstRawCharacterWhenOnlyStopWordsRemain(): void
|
|
||||||
{
|
|
||||||
$post = new Post(5, 'de la', '<p>Contenu</p>', 'slug-5');
|
|
||||||
|
|
||||||
self::assertSame('D', $this->call('post_initials', $post));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testPostInitialsUseMeaningfulWordsAndFallback(): void
|
public function testPostInitialsUseMeaningfulWordsAndFallback(): void
|
||||||
{
|
{
|
||||||
$post = new Post(1, 'Article de Blog', '<p>Contenu</p>', 'slug');
|
$post = new Post(1, 'Article de Blog', '<p>Contenu</p>', 'slug');
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ final class PostRepositoryTest extends TestCase
|
|||||||
public function testFindRecentReturnsEmptyArray(): void
|
public function testFindRecentReturnsEmptyArray(): void
|
||||||
{
|
{
|
||||||
$stmt = $this->stmtForRead([]);
|
$stmt = $this->stmtForRead([]);
|
||||||
$this->db->method('prepare')->willReturn($stmt);
|
$this->db->expects($this->once())->method('prepare')->willReturn($stmt);
|
||||||
|
|
||||||
$this->assertSame([], $this->repository->findRecent(5));
|
$this->assertSame([], $this->repository->findRecent(5));
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ final class PostRepositoryTest extends TestCase
|
|||||||
public function testFindRecentPassesLimitCorrectly(): void
|
public function testFindRecentPassesLimitCorrectly(): void
|
||||||
{
|
{
|
||||||
$stmt = $this->stmtForRead([]);
|
$stmt = $this->stmtForRead([]);
|
||||||
$this->db->method('prepare')->willReturn($stmt);
|
$this->db->expects($this->once())->method('prepare')->willReturn($stmt);
|
||||||
|
|
||||||
$stmt->expects($this->once())
|
$stmt->expects($this->once())
|
||||||
->method('bindValue')
|
->method('bindValue')
|
||||||
|
|||||||
@@ -110,6 +110,18 @@ final class PostServiceTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getPostById() retourne l'article trouvé.
|
||||||
|
*/
|
||||||
|
public function testGetPostByIdReturnsPost(): void
|
||||||
|
{
|
||||||
|
$post = $this->makePost(7, 'Titre', 'slug-7');
|
||||||
|
$this->repository->expects($this->once())->method('findById')->with(7)->willReturn($post);
|
||||||
|
|
||||||
|
self::assertSame($post, $this->service->getPostById(7));
|
||||||
|
}
|
||||||
|
|
||||||
// ── createPost ─────────────────────────────────────────────────
|
// ── createPost ─────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -217,6 +229,42 @@ final class PostServiceTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* updatePost() lève NotFoundException si la ligne disparaît entre lecture et écriture.
|
||||||
|
*/
|
||||||
|
public function testUpdatePostThrowsWhenRepositoryUpdateAffectsZeroRows(): void
|
||||||
|
{
|
||||||
|
$post = $this->makePost(3, 'Titre courant', 'titre-courant', '<p>Ancien contenu</p>');
|
||||||
|
$this->repository->expects($this->once())->method('findById')->with(3)->willReturn($post);
|
||||||
|
$this->sanitizer->method('sanitize')->willReturn('<p>Contenu sûr</p>');
|
||||||
|
$this->repository->expects($this->once())->method('update')->with(3, $this->isInstanceOf(Post::class), 'titre-courant', null)->willReturn(0);
|
||||||
|
|
||||||
|
$this->expectException(NotFoundException::class);
|
||||||
|
|
||||||
|
$this->service->updatePost(3, 'Titre courant', '<p>Contenu</p>');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* updatePost() normalise et rend unique un slug personnalisé.
|
||||||
|
*/
|
||||||
|
public function testUpdatePostUsesNormalizedUniqueCustomSlug(): void
|
||||||
|
{
|
||||||
|
$current = $this->makePost(4, 'Titre courant', 'ancien-slug', '<p>Ancien contenu</p>');
|
||||||
|
$this->repository->expects($this->once())->method('findById')->with(4)->willReturn($current);
|
||||||
|
$this->sanitizer->method('sanitize')->willReturn('<p>Contenu sûr</p>');
|
||||||
|
$this->repository->expects($this->exactly(2))
|
||||||
|
->method('slugExists')
|
||||||
|
->withAnyParameters()
|
||||||
|
->willReturnOnConsecutiveCalls(true, false);
|
||||||
|
$this->repository->expects($this->once())
|
||||||
|
->method('update')
|
||||||
|
->with(4, $this->isInstanceOf(Post::class), 'nouveau-slug-1', 2)
|
||||||
|
->willReturn(1);
|
||||||
|
|
||||||
|
$this->service->updatePost(4, 'Titre courant', '<p>Contenu</p>', ' Nouveau slug !! ', 2);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Helpers ────────────────────────────────────────────────────
|
// ── Helpers ────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,43 +6,43 @@ namespace Tests\Shared;
|
|||||||
use App\Shared\Bootstrap;
|
use App\Shared\Bootstrap;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
use ReflectionProperty;
|
||||||
use Slim\Factory\AppFactory;
|
use Slim\Factory\AppFactory;
|
||||||
|
use Slim\App;
|
||||||
|
|
||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
|
|
||||||
final class BootstrapTest extends TestCase
|
final class BootstrapTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @var array<string, string> */
|
|
||||||
private array $envBackup = [];
|
private array $envBackup = [];
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$this->envBackup = $_ENV;
|
$this->envBackup = [
|
||||||
|
'APP_AUTO_PROVISION' => $_ENV['APP_AUTO_PROVISION'] ?? null,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
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();
|
$bootstrap = Bootstrap::create();
|
||||||
$container = new class implements ContainerInterface {
|
$container = $this->createStub(ContainerInterface::class);
|
||||||
public function get(string $id): mixed
|
|
||||||
{
|
|
||||||
throw new \RuntimeException('Not expected');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function has(string $id): bool
|
$this->setPrivate($bootstrap, 'container', $container);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$this->setPrivateProperty($bootstrap, 'container', $container);
|
|
||||||
|
|
||||||
self::assertSame($container, $bootstrap->getContainer());
|
|
||||||
self::assertSame($container, $bootstrap->initializeInfrastructure());
|
self::assertSame($container, $bootstrap->initializeInfrastructure());
|
||||||
|
self::assertSame($container, $bootstrap->getContainer());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateHttpAppReturnsPreloadedApp(): void
|
public function testCreateHttpAppReturnsPreloadedApp(): void
|
||||||
@@ -50,40 +50,29 @@ final class BootstrapTest extends TestCase
|
|||||||
$bootstrap = Bootstrap::create();
|
$bootstrap = Bootstrap::create();
|
||||||
$app = AppFactory::create();
|
$app = AppFactory::create();
|
||||||
|
|
||||||
$this->setPrivateProperty($bootstrap, 'app', $app);
|
$this->setPrivate($bootstrap, 'app', $app);
|
||||||
|
|
||||||
self::assertSame($app, $bootstrap->createHttpApp());
|
self::assertSame($app, $bootstrap->createHttpApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInitializeReturnsPreloadedAppWhenAutoProvisioningDisabled(): void
|
public function testInitializeReturnsPreloadedAppWhenAutoProvisionIsDisabled(): void
|
||||||
{
|
{
|
||||||
$_ENV['APP_ENV'] = 'production';
|
|
||||||
$_ENV['APP_AUTO_PROVISION'] = '0';
|
$_ENV['APP_AUTO_PROVISION'] = '0';
|
||||||
|
|
||||||
$bootstrap = Bootstrap::create();
|
$bootstrap = Bootstrap::create();
|
||||||
$container = new class implements ContainerInterface {
|
$container = $this->createStub(ContainerInterface::class);
|
||||||
public function get(string $id): mixed
|
|
||||||
{
|
|
||||||
throw new \RuntimeException('Not expected');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function has(string $id): bool
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
$app = AppFactory::create();
|
$app = AppFactory::create();
|
||||||
|
|
||||||
$this->setPrivateProperty($bootstrap, 'container', $container);
|
$this->setPrivate($bootstrap, 'container', $container);
|
||||||
$this->setPrivateProperty($bootstrap, 'app', $app);
|
$this->setPrivate($bootstrap, 'app', $app);
|
||||||
|
|
||||||
self::assertSame($app, $bootstrap->initialize());
|
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->setAccessible(true);
|
||||||
$reflection->setValue($object, $value);
|
$reflection->setValue($bootstrap, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase;
|
|||||||
use Slim\Psr7\Factory\ServerRequestFactory;
|
use Slim\Psr7\Factory\ServerRequestFactory;
|
||||||
|
|
||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
|
|
||||||
final class ClientIpResolverTest extends TestCase
|
final class ClientIpResolverTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testResolveReturnsDefaultWhenRemoteAddrMissing(): void
|
public function testResolveReturnsDefaultWhenRemoteAddrMissing(): void
|
||||||
@@ -53,27 +54,4 @@ final class ClientIpResolverTest extends TestCase
|
|||||||
|
|
||||||
self::assertSame('127.0.0.1', $resolver->resolve($request));
|
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;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
|
|
||||||
final class ConfigTest extends TestCase
|
final class ConfigTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testGetTwigCacheReturnsFalseInDev(): void
|
public function testGetTwigCacheReturnsFalseInDev(): void
|
||||||
@@ -22,25 +23,6 @@ final class ConfigTest extends TestCase
|
|||||||
self::assertStringEndsWith('/var/cache/twig', $cachePath);
|
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
|
public function testGetDatabasePathCreatesDatabaseFileWhenMissing(): void
|
||||||
{
|
{
|
||||||
$dbFile = dirname(__DIR__, 2).'/database/app.sqlite';
|
$dbFile = dirname(__DIR__, 2).'/database/app.sqlite';
|
||||||
|
|||||||
@@ -46,21 +46,6 @@ final class ExtensionTest extends TestCase
|
|||||||
], $extension->getGlobals());
|
], $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
|
public function testCsrfExtensionExposesTokens(): void
|
||||||
{
|
{
|
||||||
$storage = [];
|
$storage = [];
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Shared\Http\FlashService;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
|
|
||||||
final class FlashServiceTest extends TestCase
|
final class FlashServiceTest extends TestCase
|
||||||
{
|
{
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
@@ -34,26 +35,6 @@ final class FlashServiceTest extends TestCase
|
|||||||
self::assertArrayNotHasKey('count', $_SESSION['flash']);
|
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
|
public function testGetReturnsNullWhenMissing(): void
|
||||||
{
|
{
|
||||||
$flash = new FlashService();
|
$flash = new FlashService();
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ namespace Tests\Shared;
|
|||||||
use App\Shared\Mail\MailService;
|
use App\Shared\Mail\MailService;
|
||||||
use PHPMailer\PHPMailer\PHPMailer;
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use ReflectionMethod;
|
||||||
use Slim\Views\Twig;
|
use Slim\Views\Twig;
|
||||||
use Twig\Loader\ArrayLoader;
|
use Twig\Loader\ArrayLoader;
|
||||||
|
|
||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
|
|
||||||
final class MailServiceTest extends TestCase
|
final class MailServiceTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testCreateMailerUsesSslConfiguration(): void
|
public function testCreateMailerUsesSslConfiguration(): void
|
||||||
{
|
{
|
||||||
$service = $this->createService('ssl', 465);
|
$service = $this->makeService('ssl', 465);
|
||||||
|
|
||||||
/** @var PHPMailer $mailer */
|
|
||||||
$mailer = $this->invokeCreateMailer($service);
|
$mailer = $this->invokeCreateMailer($service);
|
||||||
|
|
||||||
self::assertSame('smtp', $mailer->Mailer);
|
self::assertSame('smtp', $mailer->Mailer);
|
||||||
@@ -27,25 +27,23 @@ final class MailServiceTest extends TestCase
|
|||||||
self::assertSame(PHPMailer::ENCRYPTION_SMTPS, $mailer->SMTPSecure);
|
self::assertSame(PHPMailer::ENCRYPTION_SMTPS, $mailer->SMTPSecure);
|
||||||
self::assertSame(465, $mailer->Port);
|
self::assertSame(465, $mailer->Port);
|
||||||
self::assertSame(PHPMailer::CHARSET_UTF8, $mailer->CharSet);
|
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);
|
self::assertSame('Slim Blog', $mailer->FromName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateMailerUsesStartTlsWhenEncryptionIsNotSsl(): void
|
public function testCreateMailerUsesStartTlsWhenEncryptionIsNotSsl(): void
|
||||||
{
|
{
|
||||||
$service = $this->createService('tls', 587);
|
$service = $this->makeService('tls', 587);
|
||||||
|
|
||||||
/** @var PHPMailer $mailer */
|
|
||||||
$mailer = $this->invokeCreateMailer($service);
|
$mailer = $this->invokeCreateMailer($service);
|
||||||
|
|
||||||
self::assertSame(PHPMailer::ENCRYPTION_STARTTLS, $mailer->SMTPSecure);
|
self::assertSame(PHPMailer::ENCRYPTION_STARTTLS, $mailer->SMTPSecure);
|
||||||
self::assertSame(587, $mailer->Port);
|
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([
|
$twig = new Twig(new ArrayLoader([
|
||||||
'emails/test.twig' => '<p>Hello {{ name }}</p>',
|
'emails/test.twig' => '<p>Bonjour {{ name }}</p>',
|
||||||
]));
|
]));
|
||||||
|
|
||||||
return new MailService(
|
return new MailService(
|
||||||
@@ -55,16 +53,19 @@ final class MailServiceTest extends TestCase
|
|||||||
'mailer-user',
|
'mailer-user',
|
||||||
'mailer-pass',
|
'mailer-pass',
|
||||||
$encryption,
|
$encryption,
|
||||||
'noreply@example.test',
|
'no-reply@example.test',
|
||||||
'Slim Blog',
|
'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);
|
$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 App\Shared\Exception\NotFoundException;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
|
|
||||||
final class NotFoundExceptionTest extends TestCase
|
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,14 +7,13 @@ use App\Shared\Database\Provisioner;
|
|||||||
use PDO;
|
use PDO;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
|
|
||||||
final class ProvisionerTest extends TestCase
|
final class ProvisionerTest extends TestCase
|
||||||
{
|
{
|
||||||
private PDO $db;
|
private PDO $db;
|
||||||
private string $lockPath;
|
private string $lockPath;
|
||||||
private bool $lockExistedBefore;
|
private array $envBackup = [];
|
||||||
|
|
||||||
/** @var array<string, string> */
|
|
||||||
private array $envBackup;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
@@ -25,51 +24,53 @@ final class ProvisionerTest extends TestCase
|
|||||||
$this->db->sqliteCreateFunction('strip_tags', 'strip_tags', 1);
|
$this->db->sqliteCreateFunction('strip_tags', 'strip_tags', 1);
|
||||||
|
|
||||||
$this->lockPath = dirname(__DIR__, 2) . '/database/.provision.lock';
|
$this->lockPath = dirname(__DIR__, 2) . '/database/.provision.lock';
|
||||||
$this->lockExistedBefore = file_exists($this->lockPath);
|
@unlink($this->lockPath);
|
||||||
|
|
||||||
$this->envBackup = [
|
$this->envBackup = [
|
||||||
'ADMIN_USERNAME' => $_ENV['ADMIN_USERNAME'] ?? '',
|
'ADMIN_USERNAME' => $_ENV['ADMIN_USERNAME'] ?? null,
|
||||||
'ADMIN_EMAIL' => $_ENV['ADMIN_EMAIL'] ?? '',
|
'ADMIN_EMAIL' => $_ENV['ADMIN_EMAIL'] ?? null,
|
||||||
'ADMIN_PASSWORD' => $_ENV['ADMIN_PASSWORD'] ?? '',
|
'ADMIN_PASSWORD' => $_ENV['ADMIN_PASSWORD'] ?? null,
|
||||||
];
|
];
|
||||||
|
|
||||||
$_ENV['ADMIN_USERNAME'] = 'shared-admin';
|
$_ENV['ADMIN_USERNAME'] = 'Admin';
|
||||||
$_ENV['ADMIN_EMAIL'] = 'shared-admin@example.com';
|
$_ENV['ADMIN_EMAIL'] = 'Admin@example.com';
|
||||||
$_ENV['ADMIN_PASSWORD'] = 'strong-secret';
|
$_ENV['ADMIN_PASSWORD'] = 'secret1234';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
|
@unlink($this->lockPath);
|
||||||
|
|
||||||
foreach ($this->envBackup as $key => $value) {
|
foreach ($this->envBackup as $key => $value) {
|
||||||
|
if ($value === null) {
|
||||||
|
unset($_ENV[$key]);
|
||||||
|
} else {
|
||||||
$_ENV[$key] = $value;
|
$_ENV[$key] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->lockExistedBefore && file_exists($this->lockPath)) {
|
|
||||||
@unlink($this->lockPath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRunAppliesMigrationsSeedsAdminAndCreatesLockFile(): void
|
public function testRunCreatesProvisionLockAndSeedsAdminUser(): void
|
||||||
{
|
{
|
||||||
Provisioner::run($this->db);
|
Provisioner::run($this->db);
|
||||||
|
|
||||||
self::assertFileExists($this->lockPath);
|
self::assertFileExists($this->lockPath);
|
||||||
|
|
||||||
$migrationCount = (int) $this->db->query('SELECT COUNT(*) FROM migrations')->fetchColumn();
|
$row = $this->db->query('SELECT username, email, role FROM users')->fetch();
|
||||||
self::assertGreaterThan(0, $migrationCount, 'Les migrations doivent être enregistrées');
|
|
||||||
|
|
||||||
$admin = $this->db->query("SELECT username, email, role FROM users WHERE username = 'shared-admin'")->fetch();
|
self::assertIsArray($row);
|
||||||
self::assertIsArray($admin);
|
self::assertSame('admin', $row['username']);
|
||||||
self::assertSame('shared-admin@example.com', $admin['email']);
|
self::assertSame('admin@example.com', $row['email']);
|
||||||
self::assertSame('admin', $admin['role']);
|
self::assertSame('admin', $row['role']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRunIsIdempotentForAdminSeed(): void
|
public function testRunIsIdempotent(): void
|
||||||
{
|
{
|
||||||
Provisioner::run($this->db);
|
Provisioner::run($this->db);
|
||||||
Provisioner::run($this->db);
|
Provisioner::run($this->db);
|
||||||
|
|
||||||
$adminCount = (int) $this->db->query("SELECT COUNT(*) FROM users WHERE username = 'shared-admin'")->fetchColumn();
|
$count = (int) $this->db->query('SELECT COUNT(*) FROM users WHERE username = "admin"')->fetchColumn();
|
||||||
self::assertSame(1, $adminCount);
|
|
||||||
|
self::assertSame(1, $count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use App\Shared\Routes;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Slim\Factory\AppFactory;
|
use Slim\Factory\AppFactory;
|
||||||
|
|
||||||
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
|
|
||||||
final class RoutesTest extends TestCase
|
final class RoutesTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testRegisterDeclaresExpectedPublicAndProtectedRoutes(): void
|
public function testRegisterDeclaresExpectedPublicAndProtectedRoutes(): void
|
||||||
@@ -15,46 +17,48 @@ final class RoutesTest extends TestCase
|
|||||||
Routes::register($app);
|
Routes::register($app);
|
||||||
|
|
||||||
$actual = [];
|
$actual = [];
|
||||||
|
|
||||||
foreach ($app->getRouteCollector()->getRoutes() as $route) {
|
foreach ($app->getRouteCollector()->getRoutes() as $route) {
|
||||||
$pattern = $route->getPattern();
|
$pattern = $route->getPattern();
|
||||||
$methods = $route->getMethods();
|
$methods = array_values(array_diff($route->getMethods(), ['HEAD', 'OPTIONS']));
|
||||||
|
|
||||||
if (!isset($actual[$pattern])) {
|
|
||||||
$actual[$pattern] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$actual[$pattern] ??= [];
|
||||||
$actual[$pattern] = array_values(array_unique(array_merge($actual[$pattern], $methods)));
|
$actual[$pattern] = array_values(array_unique(array_merge($actual[$pattern], $methods)));
|
||||||
sort($actual[$pattern]);
|
sort($actual[$pattern]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ksort($actual);
|
||||||
|
|
||||||
$expected = [
|
$expected = [
|
||||||
'/' => ['GET'],
|
'/' => ['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'],
|
'/article/{slug}' => ['GET'],
|
||||||
'/rss.xml' => ['GET'],
|
|
||||||
'/auth/login' => ['GET', 'POST'],
|
'/auth/login' => ['GET', 'POST'],
|
||||||
'/auth/logout' => ['POST'],
|
'/auth/logout' => ['POST'],
|
||||||
'/password/forgot' => ['GET', 'POST'],
|
'/password/forgot' => ['GET', 'POST'],
|
||||||
'/password/reset' => ['GET', 'POST'],
|
'/password/reset' => ['GET', 'POST'],
|
||||||
'/account/password' => ['GET', 'POST'],
|
'/rss.xml' => ['GET'],
|
||||||
'/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'],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
foreach ($expected as $pattern => $methods) {
|
||||||
|
sort($methods);
|
||||||
|
}
|
||||||
ksort($expected);
|
ksort($expected);
|
||||||
ksort($actual);
|
|
||||||
|
|
||||||
self::assertSame($expected, $actual);
|
self::assertSame($expected, $actual);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Shared\Http\SessionManager;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
|
||||||
|
|
||||||
final class SessionManagerEdgeCasesTest extends TestCase
|
final class SessionManagerEdgeCasesTest extends TestCase
|
||||||
{
|
{
|
||||||
private SessionManager $manager;
|
private SessionManager $manager;
|
||||||
@@ -30,14 +31,6 @@ final class SessionManagerEdgeCasesTest extends TestCase
|
|||||||
self::assertFalse($this->manager->isAuthenticated());
|
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
|
public function testSetUserUsesDefaultRoleUser(): void
|
||||||
{
|
{
|
||||||
$this->manager->setUser(12, 'julien');
|
$this->manager->setUser(12, 'julien');
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ final class UserRepositoryTest extends TestCase
|
|||||||
public function testFindByIdReturnsNullWhenMissing(): void
|
public function testFindByIdReturnsNullWhenMissing(): void
|
||||||
{
|
{
|
||||||
$stmt = $this->stmtForRead(row: false);
|
$stmt = $this->stmtForRead(row: false);
|
||||||
$this->db->method('prepare')->willReturn($stmt);
|
$this->db->expects($this->once())->method('prepare')->willReturn($stmt);
|
||||||
|
|
||||||
$this->assertNull($this->repository->findById(99));
|
$this->assertNull($this->repository->findById(99));
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ final class UserRepositoryTest extends TestCase
|
|||||||
public function testFindByIdReturnsUserWhenFound(): void
|
public function testFindByIdReturnsUserWhenFound(): void
|
||||||
{
|
{
|
||||||
$stmt = $this->stmtForRead(row: $this->rowAlice);
|
$stmt = $this->stmtForRead(row: $this->rowAlice);
|
||||||
$this->db->method('prepare')->willReturn($stmt);
|
$this->db->expects($this->once())->method('prepare')->willReturn($stmt);
|
||||||
|
|
||||||
$result = $this->repository->findById(1);
|
$result = $this->repository->findById(1);
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ final class UserRepositoryTest extends TestCase
|
|||||||
public function testFindByIdQueriesWithCorrectId(): void
|
public function testFindByIdQueriesWithCorrectId(): void
|
||||||
{
|
{
|
||||||
$stmt = $this->stmtForRead(row: false);
|
$stmt = $this->stmtForRead(row: false);
|
||||||
$this->db->method('prepare')->willReturn($stmt);
|
$this->db->expects($this->once())->method('prepare')->willReturn($stmt);
|
||||||
|
|
||||||
$stmt->expects($this->once())
|
$stmt->expects($this->once())
|
||||||
->method('execute')
|
->method('execute')
|
||||||
|
|||||||
@@ -130,20 +130,7 @@
|
|||||||
serverPath: '/admin/media/upload',
|
serverPath: '/admin/media/upload',
|
||||||
fileFieldName: 'file',
|
fileFieldName: 'file',
|
||||||
urlPropertyName: 'url',
|
urlPropertyName: 'url',
|
||||||
statusPropertyName: 'success',
|
statusPropertyName: 'success'
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
name: '{{ csrf.keys.name }}',
|
|
||||||
value: '{{ csrf.name }}'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '{{ csrf.keys.value }}',
|
|
||||||
value: '{{ csrf.value }}'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user