mazarata's avatar

Feature Test on ACL using AuthServiceProvider to define Gate

Hi all

I am implementing an ACL along Jeff's explanations in https://laracasts.com/series/whats-new-in-laravel-5-1/episodes/16. So I have setup a roles and a permissions table with two pivot-tables permission_role and role_user. In the boot method of the AuthServiceProvider I have defined the Gate as follows:


 public function boot()
    {
        $this->registerPolicies();

        /**
         * boot is called also when migration is run, hence it will not find the table permissions and throw
         * an exception.
         */
        if(Schema::hasTable('permissions')){
            foreach($this->getPermissions() as $permission){
                Gate::define($permission->name, function($user) use ($permission){
                   return $user->hasRole($permission->roles);
                });
            }
        }
    }

    protected function getPermissions(){
        return Permission::with('roles')->get();
    }

I need to do the check on the existence of the table 'permissions'. Otherwise the getPermission method throws an Exception during migration, since no 'permissions' table is found. I need to migrate manually incl. prepared seeders. The acl-relevant migrations are stored in a subfolder of the migrations folder to keep them outside of the regular migrations. The DatabaseMigrations trait does not work in this feauture test.

function a_school_owner_can_create_edit_view_and_delete_a_school()
    {
        $user = factory(User::class)->create();
        $user->assignRole('school-owner');

        Auth::loginUsingId($user->id);

        $this->assertTrue(Auth::user()->can('create-school'));
        $this->assertTrue(Auth::user()->can('view-school'));
        $this->assertTrue(Auth::user()->can('edit-school'));
        $this->assertTrue(Auth::user()->can('delete-school'));

        Auth::logout();
    }

My question: the $user in the callback function of the Gate definition is null all the time, despite my Auth::loginUsingId($user->id), which actually returns the proper user. I am a bit confused on why that could be. Any help much appreciated. Thanks in advance.

0 likes
2 replies
tobiasj's avatar

Use $this->actingAs($user) within tests. This will log you in as the user given. Hope that fixes your problem.

Update: didn't read properly. Don't have a good answer yet. The boot method is ofc executed one the app is ready to go. While in a test the application is ready when "entering" the specific test function. So while your app defines the permissions there's no user logged in.

Took a quick look on the git repo. Are you calling registerPolicies properly? In the repo, provided by jeff it is parent::registerPolicies($gate); while in your boot method you say $this->registerPolicies();.

mazarata's avatar
mazarata
OP
Best Answer
Level 14

Thanks @tobiasj for your reply and thoughts. The issue is resolved. My bad. I did not implement the possiblity that the hasRole method on the User may also receive a collection. After reviewing Jeff's video a bit more attentively, I recognized having forgotten the intersection-check on a possible collection as argument. So the hasRole method now properly looks like this:

    public function hasRole($role){
        if(is_string($role)){
            return $this->roles->contains('name', $role);
        }
        return !! $role->intersect($this->roles)->count();
    }

With this adjustment, the test passes successfully.

As for your input, following feedback from my side:

  1. am very new to Laravel and am using 5.4.19. There must have been a change in the AuthServiceProvider some point back, which I could not determine exactly, however between 5.1 and 5.4. The GateContract is no longer passed to the AuthServiceProvider and so the $gate argument you refer to is also missing in the registerPolicies parent-call.

  2. Your argumentation that AuthServiceProvider is booted on a new applications setup makes sense to me, and that the user does not yet exist, too. However, I was confused a bit, since this is exactly what Jeff does in his demo as well. Booting up the application and later setup a user. It is obvious that I do not really fully understand how things work in laravel yet, will need to dig into it a bit deeper. Anyone with a good "ServiceProviders for Dummies" explanation is very welcome.

Thanks again and have good one. Cheers

Please or to participate in this conversation.