Skip to content

Testing Authorization ​

The SDK's scenario DSL builds partners, roles, groups and grants in a real database. It resolves them through the real engine and lets you ask what an actor may do. Don't mock the authorization engine: describe the situation, then ask.

The DSL is public but open by design, so it may still change.

Setup ​

The SDK ships the builder, not a helper function. Add one to tests/Pest.php:

php
use LaravelUi5\Sdk\Testing\Scenario;
use LaravelUi5\Sdk\Testing\ScenarioBuilder;

function sdk(): ScenarioBuilder
{
    return new ScenarioBuilder(new Scenario());
}

The tests need:

  • a database with the SDK's migrations, which load automatically, and RefreshDatabase
  • the modules under test
  • an i18n/i18n.properties in every app of those modules
  • every role the abilities name, declared with #[Role]

resolve() runs the sync for those modules and creates a platform owner if the database has none.

A first test ​

php
use LaravelUi5\Sdk\Settings\Enums\SystemLevel;

it('lets an accountant close a period', function () {
    $sdk = sdk()
        ->module(AccountingModule::class)
        ->partner('alice', SystemLevel::User)
        ->grantRole(AcmeRole::Accounting, 'alice')
        ->resolve();

    expect($sdk->actor('alice')->canExecute(ClosePeriodAction::class))->toBeTrue();
});

Building a scenario ​

MethodDoes
module($class), modules([...])loads the modules under test
partner($name, SystemLevel $level)creates a partner at that level
group($name, function (GroupScopeBuilder $group) { … })creates a group; inside, $group->role(…) and $group->ability($class, $ability)
grantRole($role, $partner)assigns a role
grantAbility($class, $ability, $partner)grants an ability directly
grantGroup($group, $partner)adds the partner to a group
at(Carbon $at)sets the moment to resolve at
resolve()builds everything and returns the context to ask

One convenience that the application does not have: partner() also grants the role named like the level, for example local_admin for SystemLevel::LocalAdmin. In the running application the system level grants nothing (Partners).

Asking ​

$sdk->actor('alice') answers:

MethodAnswers
canAccess(InvoicingApp::class)the artifact's Access
canExecute(ClosePeriodAction::class)the action's Act
can('act.closePeriod')any ability by name, as type.name: act.…, access.…, read.…, or see.<view>.<control> for a See ability, whose name is the view's full name and the control id. A name the app does not declare throws. Since SDK 1.2.0; before, only Act abilities answered.
hasEntry($id)whether a navigation entry is present
get, set, reset, editable, snapshotsettings, as that actor sees them

Time ​

Each grant method takes an optional window, and at() sets the moment:

php
use Illuminate\Support\Carbon;

it('grants from February on', function () {
    $sdk = sdk()
        ->at(Carbon::parse('2026-01-15'))
        ->module(AccountingModule::class)
        ->partner('alice', SystemLevel::User)
        ->grantRole(AcmeRole::Accounting, 'alice', validFrom: Carbon::parse('2026-02-01'))
        ->resolve();

    expect($sdk->actor('alice')->canExecute(ClosePeriodAction::class))->toBeFalse();
});

Both bounds of a window are inclusive (Time-Aware Grants).