DirectoryTree / Authorization

Easy, native Laravel user authorization.
MIT License
162 stars 7 forks source link

[Help] Question about testing #4

Closed benjivm closed 2 years ago

benjivm commented 2 years ago

Hi, I'm having a bit of trouble with testing routes protected by permissions.

First, I am not using the provided permission middleware, I am using Laravel's can so that super users need not pass individual permission checks:

Route::group(['middleware' => 'can:test permission'] ...

Super user middleware:

Gate::before(fn ($user) => $user->hasRole('super admin') ? true : null);

Test class:

protected function setUp() : void
{
    parent::setUp();

    app(PermissionRegistrar::class)->register();
}

public function testThePageLoads()
{
    $user = User::factory()->create();
    $permission = Permission::create(['name' => 'test permission']);

    $user->grant($permission);

    $response = $this->actingAs($user)->get(route('test.index'));

    $response
        ->assertStatus(200)
        ->assertInertia(fn (Assert $page) => $page->component('Pages/Test'));
}

This test fails with a 403. Oddly on my frontend it works fine, additionally switching to the permission middleware fixes the test, but breaks the super user middleware check and therefore the frontend app as well for users who are not explicitly given permissions (role only).

stevebauman commented 2 years ago

Hi @benjivm!

Hmm, I’m wondering if Laravel is parsing the space as an argument separator in the permission’s name in the “can” middleware.

Can you replace the space in the permission’s name with a period and give it another shot?

benjivm commented 2 years ago

Still getting a 403.

Update: this has something to do with the way Laravel is booting I think... I am using :memory: sqlite db for testing, and changing my test to this fixes it:

public function testThePageLoads()
{
    $user = User::factory()->create();
    $permission = Permission::create(['name' => 'test permission']);

    // Register permissions after creating them
    app(PermissionRegistrar::class)->register();

    $user->grant($permission);

    $response = $this->actingAs($user)->get(route('test.index'));

    $response
        ->assertStatus(200)
        ->assertInertia(fn (Assert $page) => $page->component('Pages/Test'));
}

Having to call the register() for every permission change in each test is going to be somewhat annoying, do you know what might be causing this?

Actually I suppose I could create any/all of the permissions needed in a given test suite in setUp() and then call register() there.

stevebauman commented 2 years ago

Hi @benjivml, apologies for the late reply.

The permission registrar must be registered after permissions exist in the database. Otherwise no gates will be registered in Laravel, since no permissions exist yet.

I've always seeded permissions (via a DB seeder) and then registered them prior to running tests. Creating new permissions on demand will require the re-registering them in the PermissionRegistrar.

If you have to create new ones on demand, I'd recommend creating a test helper function to do what you need (maybe on your base TestCase class):

class TestCase extends BaseTestCase
{
    protected function createPermission($name)
    {
        $permission = Permission::create(compact('name'));

        app(PermissionRegistrar::class)->register();

        return $permission;
    }
}
benjivm commented 2 years ago

Got it, and I like your solution, thanks!