Max Shaian
Max Shaian

Reputation: 478

A Laravel policy works differently on tests

Laravel 7. I user http tests to check the availability status of my pages. But I noticed that in tests one of my policy works differently.

The controller method:

public function show(Competition $competition)
{
    $competition->load('registeredTeams.user', 'teams.user');

    return view('competitions.show', compact('competition'));
}

The policy method applied to it:

public function view(User $user, Competition $competition)
{
    return $user->id === $competition->user_id;
}

The test and its internal methods:

public function testCompetitionsPageTest()
{
    $user = $this->loginAsFakeUser();
    $user->commissioner = true;
    $user->save();
    $competition = $this->createFakeCompetition($user->id);
    $this->get('/competitions')->assertOk();
    $this->get('/competitions/create')->assertOk();
    $this->get('/competitions/' . $competition->id)->assertOk();
}

protected function loginAsFakeUser()
{
    $user = User::create(['username' => 'TestUserForTests']);
    $this->actingAs($user);
    return $user;
}

protected function createFakeCompetition(int $user_id)
{
    $competition = new Competition([
        'name' => 'TestCompetitionName',
        'type' => competitionTypes()[2],
        'tops_number' => 1,
        'self_confirm' => 1,
        'winner_points' => 3,
        'registration_end' => today()->toDateString(),
    ]);
    $competition->user_id = $user_id;
    $competition->save();
    return $competition;
}

When I load the tested pages in a browser, everything works just fine. However, when I test it, the next assertion doesn't work as it returns 403 code instead of 200:

$this->get('/competitions/' . $competition->id)->assertOk();

After some manipulations I figured out that it works correctly if I use non-strict comparison in my policy:

return $user->id === $competition->user_id;

Can you help me to figure out why my created user through create() and actingAs($user) id and user_id field of $competition have different types?

Edited (added requested information): My competition migration:

    Schema::create('competitions', function (Blueprint $table) {
        $table->id();
        $table->string('name')->unique();
        $table->string('info', 1000)->nullable();
        $table->string('logo')->nullable();
        $table->foreignId('user_id')->constrained();
        $table->string('type');
        $table->json('parameters')->nullable();
        $table->unsignedTinyInteger('self_confirm');
        $table->unsignedTinyInteger('tops_number');
        $table->unsignedTinyInteger('winner_points');
        $table->unsignedSmallInteger('round')->default(0);
        $table->date('registration_end');
        $table->unsignedSmallInteger('max_teams')->default(0);
        $table->date('finished')->nullable();
        $table->timestamps();
    });

Competition model (it contains a lot of methods to request and calculate additional data only, I included only properties):

protected $casts = [
    'parameters' => 'object',
];

protected $fillable = [
    'name',
    'info',
    'registration_end',
    'self_confirm',
    'winner_points',
    'tops_number',
    'type',
];

Update

I tested the values on different stages and it turned out that for some reason during the test an injected Model Competition has user_id property with the type of string for some reason. In a browser it is an integer value as expected.

Upvotes: 0

Views: 704

Answers (2)

Max Shaian
Max Shaian

Reputation: 478

I found the problem. It was because of my test database type. It used sqlite, which has only string values in it after retrieving results. I just removed two lines from my phpunit.xml in order to use my main connection for tests as well.

Removed lines: <server name="DB_CONNECTION" value="sqlite"/> <server name="DB_DATABASE" value=":memory:"/>

Upvotes: 0

Antonio
Antonio

Reputation: 51

Just making some assumptions on the code that you posted I think that, outside the test, $competition->user_id is a string value while inside the test it's an int value. You can easily verify this assumption.

This happens because createFakeCompetition forces the type int for $user_id, as you are running without strict mode, PHP converts the string passed to an inteteger, however this makes the comparison $user->id === $competition->user_id fail.

I hope to have been clear.

Upvotes: 1

Related Questions