271 lines
9.7 KiB
PHP
271 lines
9.7 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.
|
|
*/
|
|
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->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->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->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->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));
|
|
}
|
|
}
|