ahinkle's avatar

How to HTTP test a registered policy?

I have a registered PostPolicy:

class PostPolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can delete the post.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Post  $post
     * @return \Illuminate\Auth\Access\Response|bool
     */
    public function delete(User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }
}

and it's registered in my AuthServiceProvider policies array:

    protected $policies = [
        Post::class => PostPolicy::class,
    ];

It works when manually attempting to delete another user in the browser, but it doesn't work when I'm writing a feature test:

    public function test_it_doesnt_allow_user_to_delete_different_user_post()
    {
        $u = User::factory()->create();
        $p = Post::factory()->create();

        $this
            ->actingAs($u)
            ->deleteJson(route('api.post.destroy', $p->id))
            ->assertForbidden();
    }

    // Expected response status code [403] but received 204.
    // Failed asserting that 403 is identical to 204.

This works in the browser but not with PHPUnit. I found in a different post that I can unit test the policy directly, but what would prevent someone from removing the registered policy? Then the test would still pass -- the HTTP route would ultimately fail, allowing users to delete other users' posts. Any tips here on how to feature test this?

Thanks!

0 likes
3 replies
lbecket's avatar

Would it be sufficient to test your policy by asserting that the delete method returns the expected result for a given user and post? This way you ensure that the behavior is what you expect, even if someone removes the policy.

public function test_user_can_delete_own_post()
{
    $user = User::factory()->create();
    $post = Post::factory()->create(['user_id' => $user->id]);

    $policy = new PostPolicy();
    $result = $policy->delete($user, $post);

    $this->assertTrue($result);
}

public function test_user_cannot_delete_other_user_post()
{
    $user = User::factory()->create();
    $otherUser = User::factory()->create();
    $post = Post::factory()->create(['user_id' => $otherUser->id]);

    $policy = new PostPolicy();
    $result = $policy->delete($user, $post);

    $this->assertFalse($result);
}
ahinkle's avatar

@lbecket Yeah, that wouldn't solve if someone inadvertently de-registered the policy, correct? The tests wouldn't have caught that (as they would pass with the tests above), and the API would allow for the deletion of an unauthorized user.

lbecket's avatar

Fair point. What if you added a test to ensure that the policy is registered in the AuthServiceProvider policies array? This would ensure that the policy is both functional and registered. So, something like:

public function test_post_policy_is_registered()
{
    $this->assertArrayHasKey(Post::class, $this->app->make(AuthServiceProvider::class)->policies);
    $this->assertEquals(PostPolicy::class, $this->app->make(AuthServiceProvider::class)->policies[Post::class]);
}
1 like

Please or to participate in this conversation.