Files
slim-blog/tests/User/UserServiceTest.php
2026-03-16 02:33:18 +01:00

272 lines
9.9 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\User;
use App\User\Exception\DuplicateEmailException;
use App\User\Exception\DuplicateUsernameException;
use App\User\Exception\InvalidRoleException;
use App\User\Exception\WeakPasswordException;
use App\User\User;
use App\User\UserRepositoryInterface;
use App\User\UserService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
/**
* Tests unitaires pour UserService.
*
* Vérifie la création de compte : normalisation, unicité du nom d'utilisateur
* et de l'email, validation de la complexité du mot de passe.
* Les dépendances sont remplacées par des mocks via leurs interfaces pour
* isoler le service.
*/
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class UserServiceTest extends TestCase
{
/** @var UserRepositoryInterface&MockObject */
private UserRepositoryInterface $userRepository;
private UserService $service;
protected function setUp(): void
{
$this->userRepository = $this->createMock(UserRepositoryInterface::class);
$this->service = new UserService($this->userRepository);
}
// ── createUser ─────────────────────────────────────────────────
/**
* createUser() doit créer et retourner un utilisateur avec les bonnes données.
*/
public function testCreateUserWithValidData(): void
{
$this->userRepository->method('findByUsername')->willReturn(null);
$this->userRepository->method('findByEmail')->willReturn(null);
$this->userRepository->expects($this->once())->method('create');
$user = $this->service->createUser('Alice', 'alice@example.com', 'motdepasse1');
$this->assertSame('alice', $user->getUsername());
$this->assertSame('alice@example.com', $user->getEmail());
$this->assertSame(User::ROLE_USER, $user->getRole());
}
/**
* createUser() doit normaliser le nom d'utilisateur et l'email en minuscules.
*/
public function testCreateUserNormalizesToLowercase(): void
{
$this->userRepository->method('findByUsername')->willReturn(null);
$this->userRepository->method('findByEmail')->willReturn(null);
$this->userRepository->method('create');
$user = $this->service->createUser(' ALICE ', ' ALICE@EXAMPLE.COM ', 'motdepasse1');
$this->assertSame('alice', $user->getUsername());
$this->assertSame('alice@example.com', $user->getEmail());
}
/**
* createUser() doit lever DuplicateUsernameException si le nom est déjà pris.
*/
public function testCreateUserDuplicateUsernameThrowsDuplicateUsernameException(): void
{
$existingUser = $this->makeUser('alice', 'alice@example.com');
$this->userRepository->method('findByUsername')->willReturn($existingUser);
$this->expectException(DuplicateUsernameException::class);
$this->service->createUser('alice', 'autre@example.com', 'motdepasse1');
}
/**
* createUser() doit lever DuplicateEmailException si l'email est déjà utilisé.
*/
public function testCreateUserDuplicateEmailThrowsDuplicateEmailException(): void
{
$existingUser = $this->makeUser('bob', 'alice@example.com');
$this->userRepository->method('findByUsername')->willReturn(null);
$this->userRepository->method('findByEmail')->willReturn($existingUser);
$this->expectException(DuplicateEmailException::class);
$this->service->createUser('newuser', 'alice@example.com', 'motdepasse1');
}
/**
* createUser() doit lever WeakPasswordException si le mot de passe est trop court.
*/
public function testCreateUserTooShortPasswordThrowsWeakPasswordException(): void
{
$this->userRepository->method('findByUsername')->willReturn(null);
$this->userRepository->method('findByEmail')->willReturn(null);
$this->expectException(WeakPasswordException::class);
$this->service->createUser('alice', 'alice@example.com', '1234567');
}
/**
* createUser() avec exactement 8 caractères de mot de passe doit réussir.
*/
public function testCreateUserMinimumPasswordLength(): void
{
$this->userRepository->method('findByUsername')->willReturn(null);
$this->userRepository->method('findByEmail')->willReturn(null);
$this->userRepository->method('create');
$user = $this->service->createUser('alice', 'alice@example.com', '12345678');
$this->assertInstanceOf(User::class, $user);
}
/**
* createUser() doit stocker un hash bcrypt, jamais le mot de passe en clair.
*/
public function testCreateUserPasswordIsHashed(): void
{
$plainPassword = 'motdepasse1';
$this->userRepository->method('findByUsername')->willReturn(null);
$this->userRepository->method('findByEmail')->willReturn(null);
$this->userRepository->method('create');
$user = $this->service->createUser('alice', 'alice@example.com', $plainPassword);
$this->assertNotSame($plainPassword, $user->getPasswordHash());
$this->assertTrue(password_verify($plainPassword, $user->getPasswordHash()));
}
/**
* createUser() doit attribuer le rôle passé en paramètre.
*/
public function testCreateUserWithEditorRole(): void
{
$this->userRepository->method('findByUsername')->willReturn(null);
$this->userRepository->method('findByEmail')->willReturn(null);
$this->userRepository->method('create');
$user = $this->service->createUser('alice', 'alice@example.com', 'motdepasse1', User::ROLE_EDITOR);
$this->assertSame(User::ROLE_EDITOR, $user->getRole());
}
// ── findAll ────────────────────────────────────────────────────
/**
* findAll() délègue au repository et retourne la liste.
*/
public function testFindAllDelegatesToRepository(): void
{
$users = [$this->makeUser('alice', 'alice@example.com')];
$this->userRepository->method('findAll')->willReturn($users);
$this->assertSame($users, $this->service->findAll());
}
// ── findById ───────────────────────────────────────────────────
/**
* findById() retourne null si l'utilisateur est introuvable.
*/
public function testFindByIdReturnsNullWhenMissing(): void
{
$this->userRepository->method('findById')->willReturn(null);
$this->assertNull($this->service->findById(99));
}
/**
* findById() retourne l'utilisateur trouvé.
*/
public function testFindByIdReturnsUser(): void
{
$user = $this->makeUser('alice', 'alice@example.com');
$this->userRepository->expects($this->once())->method('findById')->with(1)->willReturn($user);
$this->assertSame($user, $this->service->findById(1));
}
// ── delete ─────────────────────────────────────────────────────
/**
* delete() délègue la suppression au repository.
*/
public function testDeleteDelegatesToRepository(): void
{
$this->userRepository->expects($this->once())->method('findById')->with(5)->willReturn($this->makeUser('alice', 'alice@example.com'));
$this->userRepository->expects($this->once())->method('delete')->with(5);
$this->service->delete(5);
}
// ── updateRole ─────────────────────────────────────────────────
/**
* updateRole() doit déléguer au repository avec le rôle validé.
*/
public function testUpdateRoleDelegatesToRepository(): void
{
$this->userRepository->expects($this->once())->method('findById')->with(3)->willReturn($this->makeUser('alice', 'alice@example.com'));
$this->userRepository->expects($this->once())
->method('updateRole')
->with(3, User::ROLE_EDITOR);
$this->service->updateRole(3, User::ROLE_EDITOR);
}
/**
* updateRole() doit lever InvalidArgumentException pour un rôle inconnu.
*/
public function testUpdateRoleThrowsOnInvalidRole(): void
{
$this->userRepository->expects($this->never())->method('updateRole');
$this->expectException(InvalidRoleException::class);
$this->service->updateRole(1, 'superadmin');
}
/**
* updateRole() accepte les trois rôles valides sans lever d'exception.
*/
#[\PHPUnit\Framework\Attributes\DataProvider('validRolesProvider')]
public function testUpdateRoleAcceptsAllValidRoles(string $role): void
{
$this->userRepository->expects($this->once())->method('findById')->with(1)->willReturn($this->makeUser('alice', 'alice@example.com'));
$this->userRepository->expects($this->once())->method('updateRole')->with(1, $role);
$this->service->updateRole(1, $role);
}
/**
* @return array<string, array{string}>
*/
public static function validRolesProvider(): array
{
return [
'user' => [User::ROLE_USER],
'editor' => [User::ROLE_EDITOR],
'admin' => [User::ROLE_ADMIN],
];
}
// ── Helpers ────────────────────────────────────────────────────
/**
* Crée un utilisateur de test avec un hash bcrypt du mot de passe fourni.
*/
private function makeUser(string $username, string $email): User
{
return new User(1, $username, $email, password_hash('motdepasse1', PASSWORD_BCRYPT));
}
}