发布PhpUnit功能测试的编程身份验证用户-不受条令管理-Symfony 4.3

发布PhpUnit功能测试的编程身份验证用户-不受条令管理-Symfony 4.3,symfony,doctrine-orm,phpunit,symfony4,Symfony,Doctrine Orm,Phpunit,Symfony4,我正试图获得一个简单的“200响应”测试,用于需要经过身份验证的用户的网站部分。我想我已经创建了会话,因为在调试期间调用了控制器函数并检索了用户(使用$this->getUser()) 但是,之后该功能失败,并显示以下消息: 1) App\Tests\Controller\SecretControllerTest::testIndex200Response expected other status code for 'http://localhost/secret_url/': error:

我正试图获得一个简单的“200响应”测试,用于需要经过身份验证的用户的网站部分。我想我已经创建了会话,因为在调试期间调用了控制器函数并检索了用户(使用
$this->getUser()

但是,之后该功能失败,并显示以下消息:

1) App\Tests\Controller\SecretControllerTest::testIndex200Response
expected other status code for 'http://localhost/secret_url/':
error:
    Multiple non-persisted new entities were found through the given association graph:

 * A new entity was found through the relationship 'App\Entity\User#role' that was not configured to cascade persist operations for entity: ROLE_FOR_USER. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade
persist this association in the mapping for example @ManyToOne(..,cascade={"persist"}).
 * A new entity was found through the relationship 'App\Entity\User#secret_property' that was not configured to cascade persist operations for entity: test123. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade pe
rsist this association in the mapping for example @ManyToOne(..,cascade={"persist"}). (500 Internal Server Error)

Failed asserting that 500 matches expected 200.
如果它还没有存储在(MySQL)数据库中,并且没有使用Doctrine检索,那么这将是有意义的。记录是在每次运行/每次测试时使用夹具创建的。这就是为什么控制器中的
$This->getUser()
按预期运行

我想做的测试是:

public function testIndex200Response(): void
{
    $client = $this->getAuthenticatedSecretUserClient();

    $this->checkPageLoadResponse($client, 'http://localhost/secret_url/');
}
获取用户:

protected function getAuthenticatedSecretUserClient(): HttpKernelBrowser
{
    $this->loadFixtures(
        [
            RoleFixture::class,
            SecretUserFixture::class,
        ]
    );

    /** @var User $user */
    $user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => 'secret_user']);

    $client = self::createClient(
        [],
        [
            'PHP_AUTH_USER' => $user->getUsername(),
            'PHP_AUTH_PW'   => $user->getPlainPassword(),
        ]
    );

    $this->createClientSession($user, $client);

    return $client;
}
创建会话:

// Based on https://symfony.com/doc/current/testing/http_authentication.html#using-a-faster-authentication-mechanism-only-for-tests
protected function createClientSession(User $user, HttpKernelBrowser $client): void
{
    $authenticatedGuardToken = new PostAuthenticationGuardToken($user, 'chain_provider', $user->getRoles());
    $tokenStorage            = new TokenStorage();
    $tokenStorage->setToken($authenticatedGuardToken);

    $session = self::$container->get('session');
    $session->set('_security_<security_context>', serialize($authenticatedGuardToken));
    $session->save();

    $cookie = new Cookie($session->getName(), $session->getId());
    $client->getCookieJar()->set($cookie);

    self::$container->set('security.token_storage', $tokenStorage);
}
//基于https://symfony.com/doc/current/testing/http_authentication.html#using-a-faster-authentication-mechanism-only-for-tests
受保护函数createClientSession(用户$User,HttpKernelBrowser$client):无效
{
$authenticatedGuardToken=新的PostAuthenticationGuardToken($user,'chain_provider',$user->getRoles());
$tokenStorage=新的tokenStorage();
$tokenStorage->setToken($authenticatedGuardToken);
$session=self::$container->get('session');
$session->set(“安全性”),序列化($authenticatedGuardToken));
$session->save();
$cookie=新cookie($session->getName(),$session->getId());
$client->getCookieJar()->set($cookie);
self::$container->set('security.token\u storage',$tokenStorage);
}
这适用于创建客户端、会话和cookie

当在第一个函数中对
$url
执行请求时,它进入端点,确认用户确实已通过身份验证

根据需要,用户应该通过配置的提供者(在本例中使用条令)从中“刷新”,以检查给定对象是否与存储对象匹配

[…]在下一个请求开始时,它被反序列化,然后传递给您的用户提供商以“刷新”它(例如,针对新用户的条令查询)

我希望这也能确保会话用户被替换为条令管理的用户对象,以防止上述错误

如何解决会话中的用户在PhpUnit测试期间成为托管用户的问题?


(注意:生产代码运行时没有任何问题,此问题仅在测试期间出现(遗留代码现在开始进行测试))

好,有多个问题,但通过以下操作使其正常工作:

security:
  firewalls:
    test:
      security: ~
security:
  providers:
    test_user_provider:
      id: App\Tests\Functional\Security\UserProvider
  firewalls:
    test:
      http_basic:
        provider: test_user_provider
首先,在使用不正确的密码创建客户机时,我(在fixture中)创建了
username
password
相同的用户实体。函数
getPlainPassword
,虽然存在于接口中,但它不是存储的东西,所以是一个空值

更正代码:

$client = self::createClient(
    [],
    [
        'PHP_AUTH_USER' => $user->getUsername(),
        'PHP_AUTH_PW'   => $user->getUsername(),
    ]
);
接下来,一个没有被刷新的用户需要更多的时间

config/packages/security.yaml
中,添加以下内容:

security:
  firewalls:
    test:
      security: ~
security:
  providers:
    test_user_provider:
      id: App\Tests\Functional\Security\UserProvider
  firewalls:
    test:
      http_basic:
        provider: test_user_provider
这是为了创建“测试”密钥,因为在下一个文件中立即创建该密钥将导致权限拒绝错误。在
config/packages/test/security.yaml
中,创建以下内容:

security:
  firewalls:
    test:
      security: ~
security:
  providers:
    test_user_provider:
      id: App\Tests\Functional\Security\UserProvider
  firewalls:
    test:
      http_basic:
        provider: test_user_provider
这将添加一个定制的
UserProvider
,专门用于测试目的(因此使用
App\Tests\
名称空间)。您必须在
config/services\u test.yaml
中注册此服务:

services:
    App\Tests\Functional\Security\:
        resource: '../tests/Functional/Security'
不确定您是否需要它,但我在
config/packages/test/routing.yaml
中添加了以下内容:

parameters:
    protocol: http
由于PhpUnit是通过CLI进行测试的,所以默认情况下没有安全连接,可能因环境而异,所以请查看是否需要它

最后,在
config/packages/test/framework.yaml
中为测试框架配置:

framework:
    test: true
    session:
        storage_id: session.storage.mock_file
上述所有配置(除了
http
位)都是为了确保在测试期间使用自定义
UserProvider
提供
User
对象

这对于其他人来说可能太过分了,但是我们的设置(遗留)有一些自定义工作来为用户提供身份验证(这似乎非常相关,但远远超出了我当前的问题范围)

回到UserProvider,它的设置如下:

namespace App\Tests\Functional\Security;

use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class UserProvider implements UserProviderInterface
{
    /** @var UserRepository */
    private $userRepository;

    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function loadUserByUsername($username)
    {
        try {
            return $this->userRepository->getByUsername($username);
        } catch (UserNotFoundException $e) {
            throw new UsernameNotFoundException("Username: $username unknown");
        }
    }

    public function refreshUser(UserInterface $user)
    {
        return $this->loadUserByUsername($user->getUsername());
    }

    public function supportsClass($class)
    {
        return User::class === $class;
    }
}
注意:如果您使用此功能,您需要在UserRepository中有一个
getByUsername
函数


请注意,这可能不是您的解决方案。也许你需要改变一下,也许它完全关闭了。无论哪种方式,都会给未来的灵魂留下一个解决方案