305 lines
12 KiB
PHP
305 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Identity;
|
|
|
|
use Netig\Netslim\Identity\Application\UseCase\AdminDeleteUser;
|
|
use Netig\Netslim\Identity\Application\UseCase\AdminUpdateUserRole;
|
|
use Netig\Netslim\Identity\Application\UseCase\CreateUser;
|
|
use Netig\Netslim\Identity\Application\UseCase\DeleteUser;
|
|
use Netig\Netslim\Identity\Application\UseCase\UpdateUserRole;
|
|
use Netig\Netslim\Identity\Application\UserApplicationService;
|
|
use Netig\Netslim\Identity\Domain\Entity\User;
|
|
use Netig\Netslim\Identity\Domain\Exception\DuplicateEmailException;
|
|
use Netig\Netslim\Identity\Domain\Exception\DuplicateUsernameException;
|
|
use Netig\Netslim\Identity\Domain\Exception\InvalidRoleException;
|
|
use Netig\Netslim\Identity\Domain\Exception\WeakPasswordException;
|
|
use Netig\Netslim\Identity\Domain\Policy\PasswordPolicy;
|
|
use Netig\Netslim\Identity\Domain\Policy\RolePolicy;
|
|
use Netig\Netslim\Identity\Domain\Repository\UserRepositoryInterface;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Tests unitaires pour UserApplicationService.
|
|
*
|
|
* 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 UserApplicationService $service;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->userRepository = $this->createMock(UserRepositoryInterface::class);
|
|
$rolePolicy = new RolePolicy();
|
|
$passwordPolicy = new PasswordPolicy();
|
|
$this->service = new UserApplicationService(
|
|
$this->userRepository,
|
|
new CreateUser($this->userRepository, $rolePolicy, $passwordPolicy),
|
|
new UpdateUserRole($this->userRepository, $rolePolicy),
|
|
new DeleteUser($this->userRepository),
|
|
new AdminUpdateUserRole($this->userRepository, $rolePolicy),
|
|
new AdminDeleteUser($this->userRepository),
|
|
);
|
|
}
|
|
|
|
|
|
// ── create ─────────────────────────────────────────────────
|
|
|
|
/**
|
|
* create() 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->create('Alice', 'alice@example.com', 'motdepasse12');
|
|
|
|
$this->assertSame('alice', $user->getUsername());
|
|
$this->assertSame('alice@example.com', $user->getEmail());
|
|
$this->assertSame(User::ROLE_USER, $user->getRole());
|
|
}
|
|
|
|
/**
|
|
* create() 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->create(' ALICE ', ' ALICE@EXAMPLE.COM ', 'motdepasse12');
|
|
|
|
$this->assertSame('alice', $user->getUsername());
|
|
$this->assertSame('alice@example.com', $user->getEmail());
|
|
}
|
|
|
|
/**
|
|
* create() 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->create('alice', 'autre@example.com', 'motdepasse12');
|
|
}
|
|
|
|
/**
|
|
* create() 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->create('newuser', 'alice@example.com', 'motdepasse12');
|
|
}
|
|
|
|
/**
|
|
* create() 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->create('alice', 'alice@example.com', '12345678901');
|
|
}
|
|
|
|
/**
|
|
* create() avec exactement 12 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->create('alice', 'alice@example.com', '123456789012');
|
|
|
|
$this->assertInstanceOf(User::class, $user);
|
|
}
|
|
|
|
/**
|
|
* create() doit stocker un hash bcrypt, jamais le mot de passe en clair.
|
|
*/
|
|
public function testCreateUserPasswordIsHashed(): void
|
|
{
|
|
$plainPassword = 'motdepasse12';
|
|
|
|
$this->userRepository->method('findByUsername')->willReturn(null);
|
|
$this->userRepository->method('findByEmail')->willReturn(null);
|
|
$this->userRepository->method('create');
|
|
|
|
$user = $this->service->create('alice', 'alice@example.com', $plainPassword);
|
|
|
|
$this->assertNotSame($plainPassword, $user->getPasswordHash());
|
|
$this->assertTrue(password_verify($plainPassword, $user->getPasswordHash()));
|
|
}
|
|
|
|
/**
|
|
* create() doit conserver exactement les espaces du mot de passe saisi.
|
|
*/
|
|
public function testCreateUserPreservesPasswordWhitespace(): void
|
|
{
|
|
$plainPassword = ' motdepasse12 ';
|
|
|
|
$this->userRepository->method('findByUsername')->willReturn(null);
|
|
$this->userRepository->method('findByEmail')->willReturn(null);
|
|
$this->userRepository->method('create');
|
|
|
|
$user = $this->service->create('alice', 'alice@example.com', $plainPassword);
|
|
|
|
$this->assertTrue(password_verify($plainPassword, $user->getPasswordHash()));
|
|
$this->assertFalse(password_verify(trim($plainPassword), $user->getPasswordHash()));
|
|
}
|
|
|
|
/**
|
|
* create() 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->create('alice', 'alice@example.com', 'motdepasse12', 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 uniquement les rôles attribuables depuis l'interface.
|
|
*/
|
|
#[\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],
|
|
];
|
|
}
|
|
|
|
|
|
// ── 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('motdepasse12', PASSWORD_BCRYPT));
|
|
}
|
|
}
|