Files
michaelschiemer/tests/Unit/Framework/LiveComponents/Security/LiveComponentPermissionGuardTest.php
2025-11-24 21:28:25 +01:00

147 lines
5.2 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit\Framework\LiveComponents\Security;
require_once __DIR__ . '/TestComponents.php';
use App\Application\LiveComponents\Counter\CounterState;
use App\Framework\Attributes\Execution\AttributeExecutionContext;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\MethodName;
use App\Framework\DI\DefaultContainer;
use App\Framework\LiveComponents\Attributes\RequiresPermission;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\LiveComponents\Exceptions\UnauthorizedActionException;
use App\Framework\LiveComponents\Security\ActionAuthorizationChecker;
use App\Framework\LiveComponents\Security\Guards\LiveComponentPermissionGuard;
use App\Framework\LiveComponents\Security\LiveComponentContextHelper;
use App\Framework\LiveComponents\ValueObjects\ActionParameters;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentRenderData;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
final class LiveComponentPermissionGuardTest extends TestCase
{
private LiveComponentPermissionGuard $guard;
private ActionAuthorizationChecker&MockObject $authorizationChecker;
private DefaultContainer $container;
protected function setUp(): void
{
parent::setUp();
$this->authorizationChecker = $this->createMock(ActionAuthorizationChecker::class);
$this->container = new DefaultContainer();
$this->container->instance(ActionAuthorizationChecker::class, $this->authorizationChecker);
$this->guard = new LiveComponentPermissionGuard($this->authorizationChecker);
}
public function test_allows_authorized_action(): void
{
$component = $this->createComponent();
$params = ActionParameters::fromArray([]);
$context = $this->createContext($component, $params);
$permissionAttribute = new RequiresPermission('edit_post');
$this->authorizationChecker
->expects($this->once())
->method('isAuthorized')
->with($component, 'testAction', $permissionAttribute)
->willReturn(true);
// Should not throw
$this->guard->check($context, $permissionAttribute);
$this->assertTrue(true);
}
public function test_rejects_unauthorized_action(): void
{
$component = $this->createComponent();
$params = ActionParameters::fromArray([]);
$context = $this->createContext($component, $params);
$permissionAttribute = new RequiresPermission('edit_post');
$this->authorizationChecker
->expects($this->once())
->method('isAuthorized')
->willReturn(false);
$this->authorizationChecker
->expects($this->once())
->method('isAuthenticated')
->willReturn(true);
$this->authorizationChecker
->expects($this->once())
->method('getUserPermissions')
->willReturn(['view_post']);
$this->expectException(UnauthorizedActionException::class);
$this->guard->check($context, $permissionAttribute);
}
public function test_rejects_unauthenticated_user(): void
{
$component = $this->createComponent();
$params = ActionParameters::fromArray([]);
$context = $this->createContext($component, $params);
$permissionAttribute = new RequiresPermission('edit_post');
$this->authorizationChecker
->expects($this->once())
->method('isAuthorized')
->willReturn(false);
$this->authorizationChecker
->expects($this->once())
->method('isAuthenticated')
->willReturn(false);
$this->expectException(UnauthorizedActionException::class);
$this->expectExceptionMessage('requires authentication');
$this->guard->check($context, $permissionAttribute);
}
public function test_rejects_context_without_live_component_data(): void
{
$context = AttributeExecutionContext::forMethod(
container: $this->container,
className: ClassName::create('TestComponent'),
methodName: MethodName::create('testMethod')
);
$permissionAttribute = new RequiresPermission('edit_post');
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('LiveComponentContextData');
$this->guard->check($context, $permissionAttribute);
}
private function createComponent(): LiveComponentContract
{
return new TestComponent(
ComponentId::create('test', 'demo'),
CounterState::empty()
);
}
private function createContext(LiveComponentContract $component, ActionParameters $params): AttributeExecutionContext
{
return LiveComponentContextHelper::createForAction(
container: $this->container,
componentClass: ClassName::create($component::class),
actionMethod: MethodName::create('testAction'),
componentId: $component->id,
actionParameters: $params,
component: $component
);
}
}