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(); } }