bouncer: Subsequent protected routes test fails, but pass independently

I’m upgrading to Laravel 5.6 and v1.0.0-rc.1 from Laravel 5.5. and beta4. I’m noticing all my tests are failing that check permissions except for the first one. If a test file has 5 tests on routes that are protected by can: middleware, the first will pass and every other will fail at the $this->visit('my-url') step. If I run all the tests separately they will pass.

My version: "silber/bouncer": "v1.0.0-rc.1",

My routes:

        Route::group([
            'middleware' => ['can:'.Ability::MANAGE_GROUPS],
        ], function () {
            Route::get('groups', 'GroupController@index');
            Route::get('groups/{groupId}', 'GroupController@show');
        });

I’ve tried adding Bouncer::dontCache(); in my setUp() with no luck. Any ideas on things that might be an issue causing this or something I can look at?

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 25 (19 by maintainers)

Commits related to this issue

Most upvoted comments

@pr4xx setting out to document this, I realized there isn’t really anywhere I could point people that fully explains what the morph map is, or why you’d want to use that.

So I created a detailed article about it:

How to rid your database of PHP class names in Eloquent’s Polymorphic tables

Enjoy!

@pr4xx Thanks for the sample project ❤️


Now that I got to look at it, I actually figured out what’s going on:

  1. Bouncer registers the morph map for the role & ability models in its boot method. If a custom role/ability model has been set with Bouncer, it registers the morph map for those custom models. In hindsight this may have been a mistake, since the custom models haven’t been configured yet (so it doesn’t actually register it) as we’ll see shortly.

  2. Since the app’s service providers always run after any package service providers, your boot method runs after Bouncer’s. Since you’re setting your custom role model in your boot method, it ends up running after Bouncer has registered the morph map. At this point, your custom model does not have a morph map set up, so Bouncer::role()->getMorphClass() returns the full class name - in this case App\Role.

  3. When running the initial seeding, the DB is set up using App\Role as the morph class.

  4. When running the first test, it’s using App\Role as the morph class, so everything is working as it should.

  5. Since these Laravel feature tests all use the CreatesApplication trait, the application is rebooted before each test. This means that before the second test runs, all service providers’ register and boot methods are called again.

  6. At this point, Bouncer already has your custom role model set, so it now registers the morph map with your custom model class.

  7. Now when the second test runs, Bouncer::role()->getMorphClass() return roles instead of App\Role. Since the initial seeds all ran with App\Role as the morph class, Bouncer can’t find the relationship between the role and the abilities in the DB.


The solution to this is for you to register the morph map for your custom model on your own in your service provider:

use Bouncer;
use App\Role;
use Illuminate\Database\Eloquent\Relations\Relation;

public function boot()
{
    Bouncer::useRoleModel(Role::class);

    Relation::morphMap([Role::class]);
}

I’ll now have to figure out where to add it to the docs.

Thanks for all the help!

I understand the need to go hands on with it. I’ll see what I can do. It’s a sizable project and these failures aren’t with unit tests.