diff --git a/.gitignore b/.gitignore index f0c5241..c58e3f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ composer.lock vendor .php_cs.cache +.phpunit.result.cache diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bc5fc14 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,46 @@ +# Changelog + +[![Build Status](https://travis-ci.org/OskarStark/SonataAdminBehatContext.svg?branch=master)](https://travis-ci.org/OskarStark/SonataAdminBehatContext) + +## Upgrading from 1.x to 2.0 + +- The `friends-of-behat/symfony-extension` is used instead of `behat/symfony2-extension` (that is not maintained anymore) +- The context `SonataAdminContext` has been split into two separate contexts. + + - `SonataAdminContext` contains step definitions for `sonata-project/admin-bundle`. + - `SonataAdminUserBundleContext` contains step definitions for `sonata-project/user-bundle`. + +Before: +```yaml +# behat.yml.dist +default: + suites: + default: + contexts: + - OStark\Context\SonataAdminContext: + userManager: '@sonata.user.user_manager' + tokenStorage: '@security.token_storage' + session: '@session' +``` + +After: +```yaml +# behat.yml.dist +default: + suites: + default: + contexts: + - OStark\Context\SonataAdminContext: + # needed only if you are using sonata-project/user-bundle + - OStark\Context\SonataAdminUserBundleContext: + +# config/services_test.yaml +services: + OStark\Context\SonataAdminUserBundleContext: + arguments: + - '@sonata.user.user_manager' + - '@security.token_storage' + - '@session' + - '@service_container' + +``` diff --git a/README.md b/README.md index 13dadeb..b14b159 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ default: default: contexts: - OStark\Context\SonataAdminContext: + # this can be disabled if you do not use the SonataAdminUserBundle + - OStark\Context\SonataAdminUserBundleContext: userManager: '@sonata.user.user_manager' tokenStorage: '@security.token_storage' session: '@session' diff --git a/composer.json b/composer.json index 52340e5..daee4bf 100644 --- a/composer.json +++ b/composer.json @@ -21,14 +21,14 @@ "php": ">=7.1", "behat/behat": "^3.0", "behat/mink-extension": "^2.3", - "behat/symfony2-extension": "^2.1", - "sonata-project/user-bundle": "^4.0" + "friends-of-behat/symfony-extension": "^2.1" }, "require-dev": { "behat/mink-goutte-driver": "^1.1", "behat/mink-selenium2-driver": "^1.3", "guzzlehttp/guzzle": "^6.3", - "phpunit/phpunit": "^6.0", + "phpunit/phpunit": "^7.0|^8.5.21", + "sonata-project/user-bundle": "^4.0", "symfony/browser-kit": "^4.3" }, "config": { @@ -43,5 +43,8 @@ "psr-4": { "Tests\\OStark\\": "tests/" } + }, + "suggest": { + "sonata-project/user-bundle": "Allows User & Authentication scenarios" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 597b238..8ec4d7d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,5 +13,9 @@ tests - + + + src + + diff --git a/src/Context/SonataAdminContext.php b/src/Context/SonataAdminContext.php index a4e6f77..529768e 100644 --- a/src/Context/SonataAdminContext.php +++ b/src/Context/SonataAdminContext.php @@ -13,72 +13,22 @@ use Behat\Behat\Hook\Scope\BeforeScenarioScope; use Behat\Gherkin\Node\TableNode; -use Behat\Mink\Driver\BrowserKitDriver; -use Behat\Mink\Driver\Selenium2Driver; use Behat\Mink\Element\NodeElement; use Behat\Mink\Exception\ElementNotFoundException; use Behat\Mink\Exception\ExpectationException; -use Behat\Mink\Exception\UnsupportedDriverActionException; use Behat\MinkExtension\Context\MinkContext; use Behat\MinkExtension\Context\RawMinkContext; -use Behat\Symfony2Extension\Context\KernelAwareContext; -use Sonata\UserBundle\Model\UserManagerInterface; -use Symfony\Component\BrowserKit\Cookie; -use Symfony\Component\HttpFoundation\Session\Session; -use Symfony\Component\HttpKernel\KernelInterface; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; -use Symfony\Component\Security\Core\User\UserInterface; /** * Defines features for the SonataAdmin context. */ -final class SonataAdminContext extends RawMinkContext implements KernelAwareContext +final class SonataAdminContext extends RawMinkContext { - const DEFAULT_USERNAME = 'test@example.com'; - - /** - * @var UserInterface - */ - private $user; - - /** - * @var UserManagerInterface - */ - protected $userManager; - - /** - * @var TokenStorageInterface - */ - protected $tokenStorage; - - /** - * @var Session - */ - protected $session; - - /** - * @var KernelInterface - */ - protected $kernel; - /** * @var MinkContext */ private $minkContext; - public function __construct(UserManagerInterface $userManager, TokenStorageInterface $tokenStorage, Session $session) - { - $this->userManager = $userManager; - $this->tokenStorage = $tokenStorage; - $this->session = $session; - } - - public function setKernel(KernelInterface $kernel) - { - $this->kernel = $kernel; - } - /** * @BeforeScenario */ @@ -323,86 +273,6 @@ public function iCloseFlashMessage() $xButton->click(); } - /** - * @When /^(?:|I )delete last created user$/ - * - * @codeCoverageIgnore Selenium2Driver needed - */ - public function deleteLastCreatedUser() - { - $user = $this->userManager->findBy([], ['createdAt' => 'DESC'], 1); - $this->userManager->deleteUser(current($user)); - } - - /** - * @Given /^I am an authenticated User$/ - * - * @throws ExpectationException - * @throws UnsupportedDriverActionException - * - * @codeCoverageIgnore Selenium2Driver needed - */ - public function iAmAnAuthenticatedUser() - { - $user = $this->userManager->createUser(); - - $user->setEmail(self::DEFAULT_USERNAME); - $user->setUsername(self::DEFAULT_USERNAME); - $user->setPlainPassword('foobar'); - - $this->userManager->updateUser($user); - - $this->user = $user; - - $this->createUserSession($user); - } - - /** - * @Given /^I have role "([^"]*)"$/ - * - * @param string $role - * - * @throws ExpectationException - * @throws UnsupportedDriverActionException - * - * @codeCoverageIgnore Selenium2Driver needed - */ - public function iHaveRole($role) - { - $user = $this->getCurrentUser(); - - $user->setRoles([$role]); - $this->userManager->updateUser($user); - - $this->user = $user; - - $this->createUserSession($user); - } - - /** - * @Given /^I am authenticated as User "([^"]*)"$/ - * - * @param string $username - * - * @throws ExpectationException - * @throws UnsupportedDriverActionException - * - * @codeCoverageIgnore Selenium2Driver needed - */ - public function iAmAuthenticatedAsUser($username) - { - $driver = $this->getSession()->getDriver(); - - $user = $this->userManager->findOneBy(['username' => $username]); - if (null === $user) { - throw new ExpectationException(sprintf('User with username "%s" does not exist', $username), $driver); - } - - $this->user = $user; - - $this->createUserSession($user); - } - /** * @When /^(?:|I )logout User$/ * @@ -648,54 +518,6 @@ public function theFieldShouldNotBeEmtpy($field) $this->assertSession()->fieldValueNotEquals($field, ''); } - /** - * @throws UnsupportedDriverActionException - * - * @codeCoverageIgnore Selenium2Driver needed - */ - private function createUserSession(UserInterface $user) - { - $providerKey = $this->kernel->getContainer()->getParameter('fos_user.firewall_name'); - - $token = new UsernamePasswordToken($user, null, $providerKey, $user->getRoles()); - $this->tokenStorage->setToken($token); - - $authenticated = $this->tokenStorage->getToken()->isAuthenticated(); - if (!$authenticated) { - throw new \RuntimeException('Not authenticated!'); - } - - $this->session->set('_security_'.$providerKey, serialize($token)); - $this->session->save(); - - $driver = $this->getSession()->getDriver(); - if ($driver instanceof BrowserKitDriver) { - $client = $driver->getClient(); - $cookie = new Cookie($this->session->getName(), $this->session->getId()); - $client->getCookieJar()->set($cookie); - } elseif ($driver instanceof Selenium2Driver) { - $this->visitPath('/'); // this step is needed, otherwise the user is not logged in the first time! - } else { - throw new UnsupportedDriverActionException('The Driver is not supported!', $driver); - } - - $this->getSession()->setCookie($this->session->getName(), $this->session->getId()); - } - - private function getCurrentUser(): UserInterface - { - if (null != $this->user) { - return $this->user; - } - - $user = $this->userManager->findOneBy(['username' => self::DEFAULT_USERNAME]); - if (null === $user) { - throw new ExpectationException(sprintf('User with username "%s" does not exist', self::DEFAULT_USERNAME), $this->getSession()->getDriver()); - } - - return $user; - } - /** * Returns fixed step argument (with \\" replaced back to "). * @@ -773,9 +595,4 @@ private function notFindElement(string $locator, string $type): void throw new ExpectationException(sprintf('%s found, but should not!', $type), $this->getSession()->getDriver()); } } - - public static function getAcceptedSnippetType() - { - return 'regex'; - } } diff --git a/src/Context/SonataAdminUserBundleContext.php b/src/Context/SonataAdminUserBundleContext.php new file mode 100644 index 0000000..8dae6cd --- /dev/null +++ b/src/Context/SonataAdminUserBundleContext.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace OStark\Context; + +use Behat\Mink\Driver\BrowserKitDriver; +use Behat\Mink\Driver\Selenium2Driver; +use Behat\Mink\Exception\ExpectationException; +use Behat\Mink\Exception\UnsupportedDriverActionException; +use Behat\MinkExtension\Context\RawMinkContext; +use Sonata\UserBundle\Entity\UserManager; +use Sonata\UserBundle\Model\UserManagerInterface; +use Symfony\Component\BrowserKit\Cookie; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Defines features for the SonataAdmin context that make use of the Sonata UserBundle. + */ +final class SonataAdminUserBundleContext extends RawMinkContext +{ + const DEFAULT_USERNAME = 'test@example.com'; + + /** + * @var UserInterface + */ + private $user; + + /** + * @var UserManagerInterface + */ + protected $userManager; + + /** + * @var TokenStorageInterface + */ + protected $tokenStorage; + + /** + * @var Session + */ + protected $session; + + /** + * @var ContainerInterface + */ + protected $container; + + public function __construct( + UserManager $userManager, + TokenStorageInterface $tokenStorage, + Session $session, + ContainerInterface $container + ) { + $this->userManager = $userManager; + $this->tokenStorage = $tokenStorage; + $this->session = $session; + $this->container = $container; + } + + /** + * @When /^(?:|I )delete last created user$/ + * + * @codeCoverageIgnore Selenium2Driver needed + */ + public function deleteLastCreatedUser() + { + $user = $this->userManager->findBy([], ['createdAt' => 'DESC'], 1); + $this->userManager->deleteUser(current($user)); + } + + /** + * @Given /^I am an authenticated User$/ + * + * @throws ExpectationException + * @throws UnsupportedDriverActionException + * + * @codeCoverageIgnore Selenium2Driver needed + */ + public function iAmAnAuthenticatedUser() + { + $user = $this->userManager->createUser(); + + $user->setEmail(self::DEFAULT_USERNAME); + $user->setUsername(self::DEFAULT_USERNAME); + $user->setPlainPassword('foobar'); + + $this->userManager->updateUser($user); + + $this->user = $user; + + $this->createUserSession($user); + } + + /** + * @Given /^I have role "([^"]*)"$/ + * + * @param string $role + * + * @throws ExpectationException + * @throws UnsupportedDriverActionException + * + * @codeCoverageIgnore Selenium2Driver needed + */ + public function iHaveRole($role) + { + $user = $this->getCurrentUser(); + + $user->setRoles([$role]); + $this->userManager->updateUser($user); + + $this->user = $user; + + $this->createUserSession($user); + } + + /** + * @Given /^I am authenticated as User "([^"]*)"$/ + * + * @param string $username + * + * @throws ExpectationException + * @throws UnsupportedDriverActionException + * + * @codeCoverageIgnore Selenium2Driver needed + */ + public function iAmAuthenticatedAsUser($username) + { + $driver = $this->getSession()->getDriver(); + + $user = $this->userManager->findOneBy(['username' => $username]); + if (null === $user) { + throw new ExpectationException(sprintf('User with username "%s" does not exist', $username), $driver); + } + + $this->user = $user; + + $this->createUserSession($user); + } + + /** + * @throws UnsupportedDriverActionException + * + * @codeCoverageIgnore Selenium2Driver needed + */ + private function createUserSession(UserInterface $user) + { + $providerKey = $this->container->getParameter('fos_user.firewall_name'); + + $token = new UsernamePasswordToken($user, null, $providerKey, $user->getRoles()); + $this->tokenStorage->setToken($token); + + $authenticated = $this->tokenStorage->getToken()->isAuthenticated(); + if (!$authenticated) { + throw new \RuntimeException('Not authenticated!'); + } + + $this->session->set('_security_'.$providerKey, serialize($token)); + $this->session->save(); + + $driver = $this->getSession()->getDriver(); + if ($driver instanceof BrowserKitDriver) { + $client = $driver->getClient(); + $cookie = new Cookie($this->session->getName(), $this->session->getId()); + $client->getCookieJar()->set($cookie); + } elseif ($driver instanceof Selenium2Driver) { + $this->visitPath('/'); // this step is needed, otherwise the user is not logged in the first time! + } else { + throw new UnsupportedDriverActionException('The Driver is not supported!', $driver); + } + + $this->getSession()->setCookie($this->session->getName(), $this->session->getId()); + } + + private function getCurrentUser(): UserInterface + { + if (null != $this->user) { + return $this->user; + } + + $user = $this->userManager->findOneBy(['username' => self::DEFAULT_USERNAME]); + if (null === $user) { + throw new ExpectationException(sprintf('User with username "%s" does not exist', self::DEFAULT_USERNAME), $this->getSession()->getDriver()); + } + + return $user; + } +} diff --git a/tests/Context/SonataAdminContextTest.php b/tests/Context/SonataAdminContextTest.php index 0a41ed9..df25154 100644 --- a/tests/Context/SonataAdminContextTest.php +++ b/tests/Context/SonataAdminContextTest.php @@ -14,9 +14,6 @@ use Behat\Mink\Element\NodeElement; use OStark\Context\SonataAdminContext; use OStark\Test\BaseTestCase; -use Sonata\UserBundle\Model\UserManagerInterface; -use Symfony\Component\HttpFoundation\Session\Session; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; class SonataAdminContextTest extends BaseTestCase { @@ -25,13 +22,9 @@ class SonataAdminContextTest extends BaseTestCase */ private $context; - protected function setUp() + protected function setUp(): void { - $this->context = new SonataAdminContext( - $this->createMock(UserManagerInterface::class), - $this->createMock(TokenStorageInterface::class), - $this->createMock(Session::class) - ); + $this->context = new SonataAdminContext(); } /** diff --git a/tests/Context/SonataAdminUserBundleContextTest.php b/tests/Context/SonataAdminUserBundleContextTest.php new file mode 100644 index 0000000..de430f6 --- /dev/null +++ b/tests/Context/SonataAdminUserBundleContextTest.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tests\OStark\Context; + +use Behat\Mink\Mink; +use FOS\UserBundle\Model\UserInterface; +use OStark\Context\SonataAdminUserBundleContext; +use OStark\Test\BaseTestCase; +use PHPUnit\Framework\MockObject\MockObject; +use Sonata\UserBundle\Entity\BaseUser; +use Sonata\UserBundle\Model\UserManagerInterface; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; + +class SonataAdminUserBundleContextTest extends BaseTestCase +{ + /** + * @var SonataAdminUserBundleContext + */ + private $context; + + /** + * @var MockObject&UserManagerInterface + */ + private $userManager; + + /** + * @var MockObject&Session + */ + private $session; + + /** + * @var MockObject&TokenStorageInterface + */ + private $tokenStorage; + + /** + * @var Mink + */ + private $mink; + + protected function setUp(): void + { + $this->session = new Session(new MockArraySessionStorage()); + $this->tokenStorage = new TokenStorage(); + + $container = new Container(); + $container->setParameter('fos_user.firewall_name', 'foo'); + + $this->context = new SonataAdminUserBundleContext( + $this->userManager = $this->createMock(\Sonata\UserBundle\Entity\UserManager::class), + $this->tokenStorage, + $this->session, + $container + ); + + $this->mink = self::setupMink('

'); + $this->context->setMink($this->mink); + } + + /** + * @test + */ + public function deleteLastCreatedUser() + { + $users = [$this->createMock(UserInterface::class)]; + $this->userManager + ->expects(self::once()) + ->method('findBy') + ->with([], ['createdAt' => 'DESC'], 1) + ->willReturn($users); + + $this->userManager + ->expects(self::once()) + ->method('deleteUser') + ->with($users[0]); + + $this->context->deleteLastCreatedUser(); + } + + /** + * @test + */ + public function iAmAnAuthenticatedUser() + { + $user = new BaseUser(); + + $this->userManager + ->expects(self::once()) + ->method('createUser') + ->willReturn($user); + + $this->userManager + ->expects(self::once()) + ->method('updateUser') + ->with($user); + + $this->context->iAmAnAuthenticatedUser(); + + self::assertSame('test@example.com', $user->getUsername()); + $this->assertSessionDataValid($user); + } + + /** + * @test + */ + public function iHaveRole() + { + $user = new BaseUser(); + + $this->userManager + ->expects(self::once()) + ->method('findOneBy') + ->with(['username' => 'test@example.com']) + ->willReturn($user); + + $this->userManager + ->expects(self::never()) + ->method('createUser'); + + $this->userManager + ->expects(self::once()) + ->method('updateUser') + ->with($user); + + $this->context->iHaveRole('foo'); + + self::assertSame(['FOO', 'ROLE_USER'], $user->getRoles()); + + $this->assertSessionDataValid($user); + } + + /** + * @test + */ + public function iAmAuthenticatedAsUser() + { + $user = new BaseUser(); + + $this->userManager + ->expects(self::once()) + ->method('findOneBy') + ->with(['username' => 'me@example.com']) + ->willReturn($user); + + $this->userManager + ->expects(self::never()) + ->method('createUser'); + + $this->userManager + ->expects(self::never()) + ->method('updateUser'); + + $this->context->iAmAuthenticatedAsUser('me@example.com'); + + self::assertSame(['ROLE_USER'], $user->getRoles()); + + $this->assertSessionDataValid($user); + } + + private function assertSessionDataValid(BaseUser $user): void + { + self::assertTrue($this->session->has('_security_foo')); + self::assertSame($this->session->getId(), $this->mink->getSession()->getCookie($this->session->getName())); + self::assertInstanceOf(UsernamePasswordToken::class, $this->tokenStorage->getToken()); + self::assertSame($user, $this->tokenStorage->getToken()->getUser()); + } +}