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:
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.propertiesin 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
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
| Method | Does |
|---|---|
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:
| Method | Answers |
|---|---|
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, snapshot | settings, as that actor sees them |
Time
Each grant method takes an optional window, and at() sets the moment:
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).