133f205393
- ajoute des tests fonctionnels (archive au DELETE, exclusion de la
collection, listing/désarchivage admin, anti-auto-archivage) et un test
unitaire du ArchivedUserChecker
- expose un filtre BooleanFilter `archived` + bypass admin dans
ExcludeArchivedUserExtension pour lister les archivés (?archived=true)
- rend `archived` modifiable par un admin (groupe user:write + ApiProperty
ROLE_ADMIN) → désarchivage possible via PATCH /api/users/{id}
- RestoreMissingUsersCommand : ne compte que les insertions réelles
(ON CONFLICT DO NOTHING n'est plus comptabilisé à tort)
- relève memory_limit des tests à 512M (boot sérialiseur API Platform)
146 lines
5.1 KiB
PHP
146 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Functional\Module\Core;
|
|
|
|
use App\Module\Core\Domain\Entity\User;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
|
|
|
/**
|
|
* Covers the soft-delete behaviour: deleting a user archives it (the row is
|
|
* kept so referencing tasks/time entries still serialize), archived users are
|
|
* hidden from the default collection but an admin can list and restore them.
|
|
*
|
|
* @internal
|
|
*/
|
|
final class UserArchiveApiTest extends WebTestCase
|
|
{
|
|
public function testDeleteArchivesUserInsteadOfRemovingIt(): void
|
|
{
|
|
$client = self::createClient();
|
|
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
$target = $this->createUser($em, 'archive-target-'.uniqid());
|
|
$em->flush();
|
|
$targetId = $target->getId();
|
|
|
|
$this->loginAdmin($client);
|
|
$client->request('DELETE', '/api/users/'.$targetId);
|
|
|
|
self::assertResponseStatusCodeSame(204);
|
|
|
|
$em->clear();
|
|
$reloaded = $em->getRepository(User::class)->find($targetId);
|
|
self::assertInstanceOf(User::class, $reloaded, 'Row must still exist (soft delete)');
|
|
self::assertTrue($reloaded->isArchived(), 'User must be flagged archived');
|
|
self::assertNull($reloaded->getApiToken(), 'API token must be cleared on archive');
|
|
}
|
|
|
|
public function testAdminCannotArchiveOwnAccount(): void
|
|
{
|
|
$client = self::createClient();
|
|
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
$this->loginAdmin($client);
|
|
$adminId = $this->userId('admin');
|
|
|
|
$client->request('DELETE', '/api/users/'.$adminId);
|
|
|
|
self::assertResponseStatusCodeSame(403);
|
|
|
|
$em->clear();
|
|
$admin = $em->getRepository(User::class)->find($adminId);
|
|
self::assertFalse($admin->isArchived(), 'Admin must not have archived itself');
|
|
}
|
|
|
|
public function testArchivedUserIsHiddenFromDefaultCollection(): void
|
|
{
|
|
$client = self::createClient();
|
|
$username = $this->createArchivedUser();
|
|
|
|
$this->loginAdmin($client);
|
|
$client->request('GET', '/api/users', server: ['HTTP_ACCEPT' => 'application/json']);
|
|
|
|
self::assertResponseIsSuccessful();
|
|
$usernames = array_column(json_decode($client->getResponse()->getContent(), true), 'username');
|
|
self::assertNotContains($username, $usernames, 'Archived user must not appear in the default list');
|
|
}
|
|
|
|
public function testAdminCanListArchivedUsersViaFilter(): void
|
|
{
|
|
$client = self::createClient();
|
|
$username = $this->createArchivedUser();
|
|
|
|
$this->loginAdmin($client);
|
|
$client->request('GET', '/api/users?archived=true', server: ['HTTP_ACCEPT' => 'application/json']);
|
|
|
|
self::assertResponseIsSuccessful();
|
|
$usernames = array_column(json_decode($client->getResponse()->getContent(), true), 'username');
|
|
self::assertContains($username, $usernames, 'Admin must be able to list archived users via ?archived=true');
|
|
}
|
|
|
|
public function testAdminCanRestoreUserViaPatch(): void
|
|
{
|
|
$client = self::createClient();
|
|
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
|
|
$user = $this->createUser($em, 'restore-target-'.uniqid());
|
|
$user->setArchived(true);
|
|
$em->flush();
|
|
$userId = $user->getId();
|
|
$em->clear();
|
|
|
|
$this->loginAdmin($client);
|
|
$client->request('PATCH', '/api/users/'.$userId, server: [
|
|
'CONTENT_TYPE' => 'application/merge-patch+json',
|
|
], content: json_encode(['archived' => false]));
|
|
|
|
self::assertResponseIsSuccessful();
|
|
|
|
$em->clear();
|
|
$reloaded = $em->getRepository(User::class)->find($userId);
|
|
self::assertFalse($reloaded->isArchived(), 'Admin PATCH must be able to un-archive a user');
|
|
}
|
|
|
|
private function createArchivedUser(): string
|
|
{
|
|
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
$username = 'archived-'.uniqid();
|
|
$user = $this->createUser($em, $username);
|
|
$user->setArchived(true);
|
|
$em->flush();
|
|
$em->clear();
|
|
|
|
return $username;
|
|
}
|
|
|
|
private function createUser(EntityManagerInterface $em, string $username): User
|
|
{
|
|
$user = new User();
|
|
$user->setUsername($username);
|
|
$user->setPassword('x');
|
|
$user->setRoles(['ROLE_USER']);
|
|
$em->persist($user);
|
|
|
|
return $user;
|
|
}
|
|
|
|
private function loginAdmin(KernelBrowser $client): void
|
|
{
|
|
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
$user = $em->getRepository(User::class)->findOneBy(['username' => 'admin']);
|
|
self::assertInstanceOf(User::class, $user);
|
|
$client->loginUser($user);
|
|
}
|
|
|
|
private function userId(string $username): int
|
|
{
|
|
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
$user = $em->getRepository(User::class)->findOneBy(['username' => $username]);
|
|
self::assertInstanceOf(User::class, $user);
|
|
|
|
return $user->getId();
|
|
}
|
|
}
|