Skip to content

Commit

Permalink
[feature] add authentication assertions to KernelBrowser (#84)
Browse files Browse the repository at this point in the history
kbond authored Apr 14, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent d30d5d6 commit 582d70f
Showing 5 changed files with 146 additions and 8 deletions.
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -231,12 +231,6 @@ $browser
->assertXml()
->assertHtml()

// authenticate a user for subsequent actions
->actingAs($user) // \Symfony\Component\Security\Core\User\UserInterface

// If using zenstruck/foundry, you can pass a factory/proxy
->actingAs(UserFactory::new())

// by default, exceptions are caught and converted to a response
// use the BROWSER_CATCH_EXCEPTIONS environment variable to change default
// this disables that behaviour allowing you to use TestCase::expectException()
@@ -296,6 +290,35 @@ $browser->use(function(\Symfony\Component\HttpKernel\DataCollector\RequestDataCo
})
```

#### Authentication

The _KernelBrowser_ has helpers and assertions for authentication:

```php
/** @var \Zenstruck\Browser\KernelBrowser $browser **/

$browser
// authenticate a user for subsequent actions
->actingAs($user) // \Symfony\Component\Security\Core\User\UserInterface

// If using zenstruck/foundry, you can pass a factory/proxy
->actingAs(UserFactory::new())

// fail if authenticated
->assertNotAuthenticated()

// fail if NOT authenticated
->assertAuthenticated()

// fails if NOT authenticated as "kbond"
->assertAuthenticated('kbond')

// \Symfony\Component\Security\Core\User\UserInterface or, if using
// zenstruck/foundry, you can pass a factory/proxy
->assertAuthenticated($user)
;
```

#### HTTP Requests

The _KernelBrowser_ can be used for testing API endpoints. The following http methods are available:
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@
"symfony/framework-bundle": "^5.4|^6.0",
"symfony/polyfill-php80": "^1.20",
"zenstruck/assert": "^1.0",
"zenstruck/callback": "^1.1"
"zenstruck/callback": "^1.4.2"
},
"require-dev": {
"dbrekelmans/bdi": "^1.0",
10 changes: 10 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -130,6 +130,16 @@ parameters:
count: 1
path: src/Browser/HttpOptions.php

-
message: "#^Cannot call method getToken\\(\\) on object\\|null\\.$#"
count: 1
path: src/Browser/KernelBrowser.php

-
message: "#^Cannot call method getUserIdentifier\\(\\) on Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\TokenInterface\\|null\\.$#"
count: 1
path: src/Browser/KernelBrowser.php

-
message: "#^Method Zenstruck\\\\Browser\\\\KernelBrowser\\:\\:delete\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#"
count: 1
68 changes: 67 additions & 1 deletion src/Browser/KernelBrowser.php
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
use Symfony\Bundle\FrameworkBundle\KernelBrowser as SymfonyKernelBrowser;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Zenstruck\Assert;
use Zenstruck\Browser;
@@ -115,7 +116,9 @@ final public function withProfiling(): self
}

/**
* @param object|UserInterface|Proxy|Factory $user
* @param UserInterface|Proxy<UserInterface>|Factory<UserInterface> $user
*
* @return static
*/
public function actingAs(object $user, ?string $firewall = null): self
{
@@ -136,6 +139,58 @@ public function actingAs(object $user, ?string $firewall = null): self
return $this;
}

/**
* @param string|UserInterface|Proxy<UserInterface>|Factory<UserInterface>|null $as
*
* @return static
*/
public function assertAuthenticated($as = null): self
{
Assert::that($token = $this->securityToken())
->isNotNull('Expected to be authenticated but NOT.')
;

if (!$as) {
return $this;
}

if ($as instanceof Factory) {
$as = $as->create();
}

if ($as instanceof Proxy) {
$as = $as->object();
}

if ($as instanceof UserInterface) {
$as = $as->getUserIdentifier();
}

if (!\is_string($as)) {
throw new \LogicException(\sprintf('%s() requires the "as" user be a string or %s.', __METHOD__, UserInterface::class));
}

Assert::that($token->getUserIdentifier())
->is($as, 'Expected to be authenticated as "{expected}" but authenticated as "{actual}".')
;

return $this;
}

/**
* @return static
*/
public function assertNotAuthenticated(): self
{
Assert::that($token = $this->securityToken())
->isNull('Expected to NOT be authenticated but authenticated as "{actual}".', [
'actual' => $token ? $token->getUserIdentifier() : null,
])
;

return $this;
}

final public function profile(): Profile
{
if (!$profile = $this->client()->getProfile()) {
@@ -428,4 +483,15 @@ protected function useParameters(): array
})),
];
}

private function securityToken(): ?TokenInterface
{
$container = $this->client()->getContainer();

if (!$container->has('security.token_storage')) {
throw new \LogicException('Security not available/enabled.');
}

return $container->get('security.token_storage')->getToken();
}
}
39 changes: 39 additions & 0 deletions tests/KernelBrowserTests.php
Original file line number Diff line number Diff line change
@@ -83,6 +83,45 @@ public function can_act_as_user_with_foundry_proxy(): void
;
}

/**
* @test
*/
public function can_make_authentication_assertions(): void
{
// todo remove this requirement in foundry
Factory::boot(new Configuration());

$username = 'kevin';
$user = new InMemoryUser('kevin', 'pass');
$factory = factory(InMemoryUser::class, ['username' => 'kevin', 'password' => 'pass']);
$proxy = factory(InMemoryUser::class)->create(['username' => 'kevin', 'password' => 'pass']);

$this->browser()
->assertNotAuthenticated()
->actingAs($user)
->assertAuthenticated()
->assertAuthenticated($username)
->assertAuthenticated($user)
->assertAuthenticated($factory)
->assertAuthenticated($proxy)
->visit('/user')
->assertAuthenticated()
->assertAuthenticated($username)
;
}

/**
* @test
*/
public function can_check_if_not_authenticated_after_request(): void
{
$this->browser()
->visit('/page1')
->assertNotAuthenticated()
->assertSeeIn('a', 'a link')
;
}

/**
* @test
*/

0 comments on commit 582d70f

Please sign in to comment.