mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-06-25 22:41:57 +02:00
Refactor & Complete tests for the authentication log feature
This commit is contained in:
parent
76c3b6fe0c
commit
4987e060c4
@ -18,6 +18,7 @@ use Jenssegers\Agent\Agent;
|
|||||||
* @property Carbon|null $logout_at
|
* @property Carbon|null $logout_at
|
||||||
* @property bool $login_successful
|
* @property bool $login_successful
|
||||||
* @property string|null $duration
|
* @property string|null $duration
|
||||||
|
* @property string|null $login_method
|
||||||
*/
|
*/
|
||||||
class UserAuthentication extends JsonResource
|
class UserAuthentication extends JsonResource
|
||||||
{
|
{
|
||||||
@ -67,6 +68,7 @@ class UserAuthentication extends JsonResource
|
|||||||
'duration' => $this->logout_at
|
'duration' => $this->logout_at
|
||||||
? Carbon::parse($this->logout_at)->diffForHumans(Carbon::parse($this->login_at), ['syntax' => CarbonInterface::DIFF_ABSOLUTE])
|
? Carbon::parse($this->logout_at)->diffForHumans(Carbon::parse($this->login_at), ['syntax' => CarbonInterface::DIFF_ABSOLUTE])
|
||||||
: null,
|
: null,
|
||||||
|
'login_method' => $this->login_method,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ class FailedLoginListener extends AbstractAccessListener
|
|||||||
'login_method' => $this->loginMethod(),
|
'login_method' => $this->loginMethod(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($user->preferences['notifyOnFailedLogin']) {
|
if ($user->preferences['notifyOnFailedLogin'] == true) {
|
||||||
$user->notify(new FailedLogin($log));
|
$user->notify(new FailedLogin($log));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ class LoginListener extends AbstractAccessListener
|
|||||||
'login_method' => $this->loginMethod(),
|
'login_method' => $this->loginMethod(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (! $known && ! $newUser && $user->preferences['notifyOnNewAuthDevice']) {
|
if (! $known && ! $newUser && $user->preferences['notifyOnNewAuthDevice'] == true) {
|
||||||
$user->notify(new SignedInWithNewDevice($log));
|
$user->notify(new SignedInWithNewDevice($log));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
|
|
||||||
@ -42,6 +43,8 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
|||||||
*/
|
*/
|
||||||
class AuthLog extends Model
|
class AuthLog extends Model
|
||||||
{
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the model should be timestamped.
|
* Indicates if the model should be timestamped.
|
||||||
*/
|
*/
|
||||||
|
150
database/factories/AuthLogFactory.php
Normal file
150
database/factories/AuthLogFactory.php
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use ParagonIE\ConstantTime\Base32;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\AuthLog>
|
||||||
|
*/
|
||||||
|
class AuthLogFactory extends Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Define the model's default state.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function definition()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'ip_address' => '127.0.0.1',
|
||||||
|
'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',
|
||||||
|
'login_at' => now(),
|
||||||
|
'login_successful' => true,
|
||||||
|
'logout_at' => null,
|
||||||
|
'guard' => 'web-guard',
|
||||||
|
'login_method' => 'password',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the model is a failed login.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\AuthLog>
|
||||||
|
*/
|
||||||
|
public function failedLogin()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
return [
|
||||||
|
'login_successful' => false,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the model has a logout date only, without login date.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\AuthLog>
|
||||||
|
*/
|
||||||
|
public function logoutOnly()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
return [
|
||||||
|
'login_at' => null,
|
||||||
|
'login_successful' => false,
|
||||||
|
'logout_at' => now(),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the model has login during last month.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\AuthLog>
|
||||||
|
*/
|
||||||
|
public function duringLastMonth()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$loginDate = now()->subDays(15);
|
||||||
|
$logoutDate = $loginDate->addHours(1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'login_at' => $loginDate,
|
||||||
|
'logout_at' => $logoutDate,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the model has login during last 3 months.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\AuthLog>
|
||||||
|
*/
|
||||||
|
public function duringLastThreeMonth()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$loginDate = now()->subMonths(2);
|
||||||
|
$logoutDate = $loginDate->addHours(1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'login_at' => $loginDate,
|
||||||
|
'logout_at' => $logoutDate,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the model has login during last 6 month.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\AuthLog>
|
||||||
|
*/
|
||||||
|
public function duringLastSixMonth()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$loginDate = now()->subMonths(4);
|
||||||
|
$logoutDate = $loginDate->addHours(1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'login_at' => $loginDate,
|
||||||
|
'logout_at' => $logoutDate,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the model has login during last year.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\AuthLog>
|
||||||
|
*/
|
||||||
|
public function duringLastYear()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$loginDate = now()->subMonths(10);
|
||||||
|
$logoutDate = $loginDate->addHours(1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'login_at' => $loginDate,
|
||||||
|
'logout_at' => $logoutDate,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the model has login before last year.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\AuthLog>
|
||||||
|
*/
|
||||||
|
public function beforeLastYear()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$loginDate = now()->subYears(2);
|
||||||
|
$logoutDate = $loginDate->addHours(1);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'login_at' => $loginDate,
|
||||||
|
'logout_at' => $logoutDate,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ namespace Tests\Api\v1\Controllers;
|
|||||||
|
|
||||||
use App\Api\v1\Controllers\UserManagerController;
|
use App\Api\v1\Controllers\UserManagerController;
|
||||||
use App\Api\v1\Resources\UserManagerResource;
|
use App\Api\v1\Resources\UserManagerResource;
|
||||||
|
use App\Models\AuthLog;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Policies\UserPolicy;
|
use App\Policies\UserPolicy;
|
||||||
use Database\Factories\UserFactory;
|
use Database\Factories\UserFactory;
|
||||||
@ -22,7 +23,6 @@ use Laravel\Passport\TokenRepository;
|
|||||||
use Mockery\MockInterface;
|
use Mockery\MockInterface;
|
||||||
use PHPUnit\Framework\Attributes\CoversClass;
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
use PHPUnit\Framework\Attributes\DataProvider;
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
use Tests\Data\AuthenticationLogData;
|
|
||||||
use Tests\FeatureTestCase;
|
use Tests\FeatureTestCase;
|
||||||
|
|
||||||
#[CoversClass(UserManagerController::class)]
|
#[CoversClass(UserManagerController::class)]
|
||||||
@ -524,33 +524,38 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Local feeder because Factory cannot be used here
|
* @test
|
||||||
*/
|
*/
|
||||||
protected function feedAuthenticationLog() : int
|
public function test_authentications_returns_all_entries() : void
|
||||||
{
|
{
|
||||||
// Do not change creation order
|
AuthLog::factory()->for($this->user, 'authenticatable')->beforeLastYear()->create();
|
||||||
$this->user->authentications()->create(AuthenticationLogData::beforeLastYear());
|
AuthLog::factory()->for($this->user, 'authenticatable')->duringLastYear()->create();
|
||||||
$this->user->authentications()->create(AuthenticationLogData::duringLastYear());
|
AuthLog::factory()->for($this->user, 'authenticatable')->duringLastSixMonth()->create();
|
||||||
$this->user->authentications()->create(AuthenticationLogData::duringLastSixMonth());
|
AuthLog::factory()->for($this->user, 'authenticatable')->duringLastThreeMonth()->create();
|
||||||
$this->user->authentications()->create(AuthenticationLogData::duringLastThreeMonth());
|
AuthLog::factory()->for($this->user, 'authenticatable')->duringLastMonth()->create();
|
||||||
$this->user->authentications()->create(AuthenticationLogData::duringLastMonth());
|
AuthLog::factory()->for($this->user, 'authenticatable')->logoutOnly()->create();
|
||||||
$this->user->authentications()->create(AuthenticationLogData::noLogin());
|
AuthLog::factory()->for($this->user, 'authenticatable')->failedLogin()->create();
|
||||||
$this->user->authentications()->create(AuthenticationLogData::noLogout());
|
AuthLog::factory()->for($this->user, 'authenticatable')->create();
|
||||||
|
|
||||||
return 7;
|
$this->actingAs($this->admin, 'api-guard')
|
||||||
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications')
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonCount(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*/
|
*/
|
||||||
public function test_authentications_returns_all_entries() : void
|
public function test_authentications_returns_user_entries_only() : void
|
||||||
{
|
{
|
||||||
$created = $this->feedAuthenticationLog();
|
AuthLog::factory()->for($this->admin, 'authenticatable')->create();
|
||||||
|
AuthLog::factory()->for($this->user, 'authenticatable')->create();
|
||||||
|
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$response = $this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications')
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications')
|
||||||
->assertOk()
|
->assertJsonCount(1);
|
||||||
->assertJsonCount($created);
|
|
||||||
|
$this->assertEquals($response->getData()[0]->id, $this->user->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -558,7 +563,7 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
*/
|
*/
|
||||||
public function test_authentications_returns_expected_resource() : void
|
public function test_authentications_returns_expected_resource() : void
|
||||||
{
|
{
|
||||||
$this->user->authentications()->create(AuthenticationLogData::duringLastMonth());
|
AuthLog::factory()->for($this->user, 'authenticatable')->create();
|
||||||
|
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications')
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications')
|
||||||
@ -574,6 +579,7 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
'logout_at',
|
'logout_at',
|
||||||
'login_successful',
|
'login_successful',
|
||||||
'duration',
|
'duration',
|
||||||
|
'login_method',
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -581,12 +587,12 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*/
|
*/
|
||||||
public function test_authentications_returns_no_login_entry() : void
|
public function test_authentications_returns_loginless_entries() : void
|
||||||
{
|
{
|
||||||
$this->user->authentications()->create(AuthenticationLogData::noLogin());
|
$this->logUserOut();
|
||||||
|
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=1')
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications')
|
||||||
->assertJsonCount(1)
|
->assertJsonCount(1)
|
||||||
->assertJsonFragment([
|
->assertJsonFragment([
|
||||||
'login_at' => null,
|
'login_at' => null,
|
||||||
@ -596,12 +602,12 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*/
|
*/
|
||||||
public function test_authentications_returns_no_logout_entry() : void
|
public function test_authentications_returns_logoutless_entries() : void
|
||||||
{
|
{
|
||||||
$this->user->authentications()->create(AuthenticationLogData::noLogout());
|
$this->logUserIn();
|
||||||
|
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=1')
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications')
|
||||||
->assertJsonCount(1)
|
->assertJsonCount(1)
|
||||||
->assertJsonFragment([
|
->assertJsonFragment([
|
||||||
'logout_at' => null,
|
'logout_at' => null,
|
||||||
@ -613,14 +619,15 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
*/
|
*/
|
||||||
public function test_authentications_returns_failed_entry() : void
|
public function test_authentications_returns_failed_entry() : void
|
||||||
{
|
{
|
||||||
$this->user->authentications()->create(AuthenticationLogData::failedLogin());
|
$this->json('POST', '/user/login', [
|
||||||
$expected = Carbon::parse(AuthenticationLogData::failedLogin()['login_at'])->toDayDateTimeString();
|
'email' => $this->user->email,
|
||||||
|
'password' => 'wrong_password',
|
||||||
|
]);
|
||||||
|
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=1')
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=1')
|
||||||
->assertJsonCount(1)
|
->assertJsonCount(1)
|
||||||
->assertJsonFragment([
|
->assertJsonFragment([
|
||||||
'login_at' => $expected,
|
|
||||||
'login_successful' => false,
|
'login_successful' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -630,15 +637,16 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
*/
|
*/
|
||||||
public function test_authentications_returns_last_month_entries() : void
|
public function test_authentications_returns_last_month_entries() : void
|
||||||
{
|
{
|
||||||
$this->feedAuthenticationLog();
|
$this->travel(-2)->months();
|
||||||
$expected = Carbon::parse(AuthenticationLogData::duringLastMonth()['login_at'])->toDayDateTimeString();
|
$this->logUserInAndOut();
|
||||||
|
$this->travelBack();
|
||||||
|
$this->logUserIn();
|
||||||
|
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$response = $this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=1')
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=1')
|
||||||
->assertJsonCount(3)
|
->assertJsonCount(1);
|
||||||
->assertJsonFragment([
|
|
||||||
'login_at' => $expected,
|
$this->assertTrue(Carbon::parse($response->getData()[0]->login_at)->isSameDay(now()));
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -646,19 +654,18 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
*/
|
*/
|
||||||
public function test_authentications_returns_last_three_months_entries() : void
|
public function test_authentications_returns_last_three_months_entries() : void
|
||||||
{
|
{
|
||||||
$this->feedAuthenticationLog();
|
$this->travel(-100)->days();
|
||||||
$expectedOneMonth = Carbon::parse(AuthenticationLogData::duringLastMonth()['login_at'])->toDayDateTimeString();
|
$this->logUserInAndOut();
|
||||||
$expectedThreeMonth = Carbon::parse(AuthenticationLogData::duringLastThreeMonth()['login_at'])->toDayDateTimeString();
|
$this->travelBack();
|
||||||
|
$this->travel(-80)->days();
|
||||||
|
$this->logUserIn();
|
||||||
|
$this->travelBack();
|
||||||
|
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$response = $this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=3')
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=3')
|
||||||
->assertJsonCount(4)
|
->assertJsonCount(1);
|
||||||
->assertJsonFragment([
|
|
||||||
'login_at' => $expectedOneMonth,
|
$this->assertTrue(Carbon::parse($response->getData()[0]->login_at)->isSameDay(now()->subDays(80)));
|
||||||
])
|
|
||||||
->assertJsonFragment([
|
|
||||||
'login_at' => $expectedThreeMonth,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -666,23 +673,18 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
*/
|
*/
|
||||||
public function test_authentications_returns_last_six_months_entries() : void
|
public function test_authentications_returns_last_six_months_entries() : void
|
||||||
{
|
{
|
||||||
$this->feedAuthenticationLog();
|
$this->travel(-7)->months();
|
||||||
$expectedOneMonth = Carbon::parse(AuthenticationLogData::duringLastMonth()['login_at'])->toDayDateTimeString();
|
$this->logUserInAndOut();
|
||||||
$expectedThreeMonth = Carbon::parse(AuthenticationLogData::duringLastThreeMonth()['login_at'])->toDayDateTimeString();
|
$this->travelBack();
|
||||||
$expectedSixMonth = Carbon::parse(AuthenticationLogData::duringLastSixMonth()['login_at'])->toDayDateTimeString();
|
$this->travel(-5)->months();
|
||||||
|
$this->logUserIn();
|
||||||
|
$this->travelBack();
|
||||||
|
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$response = $this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=6')
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=6')
|
||||||
->assertJsonCount(5)
|
->assertJsonCount(1);
|
||||||
->assertJsonFragment([
|
|
||||||
'login_at' => $expectedOneMonth,
|
$this->assertTrue(Carbon::parse($response->getData()[0]->login_at)->isSameDay(now()->subMonths(5)));
|
||||||
])
|
|
||||||
->assertJsonFragment([
|
|
||||||
'login_at' => $expectedThreeMonth,
|
|
||||||
])
|
|
||||||
->assertJsonFragment([
|
|
||||||
'login_at' => $expectedSixMonth,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -690,27 +692,18 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
*/
|
*/
|
||||||
public function test_authentications_returns_last_year_entries() : void
|
public function test_authentications_returns_last_year_entries() : void
|
||||||
{
|
{
|
||||||
$this->feedAuthenticationLog();
|
$this->travel(-13)->months();
|
||||||
$expectedOneMonth = Carbon::parse(AuthenticationLogData::duringLastMonth()['login_at'])->toDayDateTimeString();
|
$this->logUserInAndOut();
|
||||||
$expectedThreeMonth = Carbon::parse(AuthenticationLogData::duringLastThreeMonth()['login_at'])->toDayDateTimeString();
|
$this->travelBack();
|
||||||
$expectedSixMonth = Carbon::parse(AuthenticationLogData::duringLastSixMonth()['login_at'])->toDayDateTimeString();
|
$this->travel(-11)->months();
|
||||||
$expectedYear = Carbon::parse(AuthenticationLogData::duringLastYear()['login_at'])->toDayDateTimeString();
|
$this->logUserIn();
|
||||||
|
$this->travelBack();
|
||||||
|
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$response = $this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=12')
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=12')
|
||||||
->assertJsonCount(6)
|
->assertJsonCount(1);
|
||||||
->assertJsonFragment([
|
|
||||||
'login_at' => $expectedOneMonth,
|
$this->assertTrue(Carbon::parse($response->getData()[0]->login_at)->isSameDay(now()->subMonths(11)));
|
||||||
])
|
|
||||||
->assertJsonFragment([
|
|
||||||
'login_at' => $expectedThreeMonth,
|
|
||||||
])
|
|
||||||
->assertJsonFragment([
|
|
||||||
'login_at' => $expectedSixMonth,
|
|
||||||
])
|
|
||||||
->assertJsonFragment([
|
|
||||||
'login_at' => $expectedYear,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -719,7 +712,10 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
#[DataProvider('LimitProvider')]
|
#[DataProvider('LimitProvider')]
|
||||||
public function test_authentications_returns_limited_entries($limit) : void
|
public function test_authentications_returns_limited_entries($limit) : void
|
||||||
{
|
{
|
||||||
$this->feedAuthenticationLog();
|
AuthLog::factory()->for($this->user, 'authenticatable')->duringLastYear()->create();
|
||||||
|
AuthLog::factory()->for($this->user, 'authenticatable')->duringLastSixMonth()->create();
|
||||||
|
AuthLog::factory()->for($this->user, 'authenticatable')->duringLastThreeMonth()->create();
|
||||||
|
AuthLog::factory()->for($this->user, 'authenticatable')->duringLastMonth()->create();
|
||||||
|
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?limit=' . $limit)
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?limit=' . $limit)
|
||||||
@ -728,7 +724,7 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide various limit
|
* Provide various limits
|
||||||
*/
|
*/
|
||||||
public static function LimitProvider()
|
public static function LimitProvider()
|
||||||
{
|
{
|
||||||
@ -736,6 +732,7 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
'limited to 1' => [1],
|
'limited to 1' => [1],
|
||||||
'limited to 2' => [2],
|
'limited to 2' => [2],
|
||||||
'limited to 3' => [3],
|
'limited to 3' => [3],
|
||||||
|
'limited to 4' => [4],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -744,13 +741,9 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
*/
|
*/
|
||||||
public function test_authentications_returns_expected_ip_and_useragent_chunks() : void
|
public function test_authentications_returns_expected_ip_and_useragent_chunks() : void
|
||||||
{
|
{
|
||||||
$this->user->authentications()->create([
|
AuthLog::factory()->for($this->user, 'authenticatable')->create([
|
||||||
'ip_address' => '127.0.0.1',
|
'ip_address' => '127.0.0.1',
|
||||||
'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',
|
'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',
|
||||||
'login_at' => now(),
|
|
||||||
'login_successful' => true,
|
|
||||||
'logout_at' => null,
|
|
||||||
'location' => null,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$this->actingAs($this->admin, 'api-guard')
|
||||||
@ -771,7 +764,7 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
{
|
{
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?limit=' . $limit)
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?limit=' . $limit)
|
||||||
->assertStatus(422);
|
->assertInvalid(['limit']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -782,7 +775,7 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
{
|
{
|
||||||
$this->actingAs($this->admin, 'api-guard')
|
$this->actingAs($this->admin, 'api-guard')
|
||||||
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=' . $period)
|
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=' . $period)
|
||||||
->assertStatus(422);
|
->assertInvalid(['period']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -798,4 +791,33 @@ class UserManagerControllerTest extends FeatureTestCase
|
|||||||
'array' => ['[]'],
|
'array' => ['[]'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a request to login the user in
|
||||||
|
*/
|
||||||
|
protected function logUserIn() : void
|
||||||
|
{
|
||||||
|
$this->json('POST', '/user/login', [
|
||||||
|
'email' => $this->user->email,
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a request to login the user out
|
||||||
|
*/
|
||||||
|
protected function logUserOut() : void
|
||||||
|
{
|
||||||
|
$this->actingAs($this->user, 'web-guard')
|
||||||
|
->json('GET', '/user/logout');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a request to login the user out
|
||||||
|
*/
|
||||||
|
protected function logUserInAndOut() : void
|
||||||
|
{
|
||||||
|
$this->logUserIn();
|
||||||
|
$this->logUserOut();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Data;
|
|
||||||
|
|
||||||
class AuthenticationLogData
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Indicate that the model should have login date.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function failedLogin()
|
|
||||||
{
|
|
||||||
$loginDate = now()->subDays(15);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'ip_address' => fake()->ipv4(),
|
|
||||||
'user_agent' => fake()->userAgent(),
|
|
||||||
'login_at' => $loginDate,
|
|
||||||
'login_successful' => false,
|
|
||||||
'logout_at' => null,
|
|
||||||
'location' => null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate that the model should have no login date
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function noLogin()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'ip_address' => fake()->ipv4(),
|
|
||||||
'user_agent' => fake()->userAgent(),
|
|
||||||
'login_at' => null,
|
|
||||||
'login_successful' => false,
|
|
||||||
'logout_at' => now(),
|
|
||||||
'location' => null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate that the model should have no logout date
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function noLogout()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'ip_address' => fake()->ipv4(),
|
|
||||||
'user_agent' => fake()->userAgent(),
|
|
||||||
'login_at' => now(),
|
|
||||||
'login_successful' => true,
|
|
||||||
'logout_at' => null,
|
|
||||||
'location' => null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate that the model should have login during last month
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function duringLastMonth()
|
|
||||||
{
|
|
||||||
$loginDate = now()->subDays(15);
|
|
||||||
$logoutDate = $loginDate->addHours(1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'ip_address' => '127.0.0.1',
|
|
||||||
'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',
|
|
||||||
'login_at' => $loginDate,
|
|
||||||
'login_successful' => true,
|
|
||||||
'logout_at' => $logoutDate,
|
|
||||||
'location' => null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate that the model should have login during last 3 month
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function duringLastThreeMonth()
|
|
||||||
{
|
|
||||||
$loginDate = now()->subMonths(2);
|
|
||||||
$logoutDate = $loginDate->addHours(1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'ip_address' => fake()->ipv4(),
|
|
||||||
'user_agent' => fake()->userAgent(),
|
|
||||||
'login_at' => $loginDate,
|
|
||||||
'login_successful' => true,
|
|
||||||
'logout_at' => $logoutDate,
|
|
||||||
'location' => null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate that the model should have login during last 6 month
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function duringLastSixMonth()
|
|
||||||
{
|
|
||||||
$loginDate = now()->subMonths(4);
|
|
||||||
$logoutDate = $loginDate->addHours(1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'ip_address' => fake()->ipv4(),
|
|
||||||
'user_agent' => fake()->userAgent(),
|
|
||||||
'login_at' => $loginDate,
|
|
||||||
'login_successful' => true,
|
|
||||||
'logout_at' => $logoutDate,
|
|
||||||
'location' => null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate that the model should have login during last month
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function duringLastYear()
|
|
||||||
{
|
|
||||||
$loginDate = now()->subMonths(10);
|
|
||||||
$logoutDate = $loginDate->addHours(1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'ip_address' => fake()->ipv4(),
|
|
||||||
'user_agent' => fake()->userAgent(),
|
|
||||||
'login_at' => $loginDate,
|
|
||||||
'login_successful' => true,
|
|
||||||
'logout_at' => $logoutDate,
|
|
||||||
'location' => null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate that the model should have login during last month
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function beforeLastYear()
|
|
||||||
{
|
|
||||||
$loginDate = now()->subYears(2);
|
|
||||||
$logoutDate = $loginDate->addHours(1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'ip_address' => fake()->ipv4(),
|
|
||||||
'user_agent' => fake()->userAgent(),
|
|
||||||
'login_at' => $loginDate,
|
|
||||||
'login_successful' => true,
|
|
||||||
'logout_at' => $logoutDate,
|
|
||||||
'location' => null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,9 +7,14 @@ use App\Http\Middleware\RejectIfAuthenticated;
|
|||||||
use App\Http\Middleware\RejectIfDemoMode;
|
use App\Http\Middleware\RejectIfDemoMode;
|
||||||
use App\Http\Middleware\RejectIfReverseProxy;
|
use App\Http\Middleware\RejectIfReverseProxy;
|
||||||
use App\Http\Middleware\SkipIfAuthenticated;
|
use App\Http\Middleware\SkipIfAuthenticated;
|
||||||
|
use App\Listeners\Authentication\FailedLoginListener;
|
||||||
|
use App\Listeners\Authentication\LoginListener;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Notifications\FailedLogin;
|
||||||
|
use App\Notifications\SignedInWithNewDevice;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
use PHPUnit\Framework\Attributes\CoversClass;
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
use Tests\FeatureTestCase;
|
use Tests\FeatureTestCase;
|
||||||
|
|
||||||
@ -21,6 +26,8 @@ use Tests\FeatureTestCase;
|
|||||||
#[CoversClass(RejectIfReverseProxy::class)]
|
#[CoversClass(RejectIfReverseProxy::class)]
|
||||||
#[CoversClass(RejectIfDemoMode::class)]
|
#[CoversClass(RejectIfDemoMode::class)]
|
||||||
#[CoversClass(SkipIfAuthenticated::class)]
|
#[CoversClass(SkipIfAuthenticated::class)]
|
||||||
|
#[CoversClass(LoginListener::class)]
|
||||||
|
#[CoversClass(FailedLoginListener::class)]
|
||||||
class LoginTest extends FeatureTestCase
|
class LoginTest extends FeatureTestCase
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -70,6 +77,63 @@ class LoginTest extends FeatureTestCase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_login_send_new_device_notification()
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$this->json('POST', '/user/login', [
|
||||||
|
'email' => $this->user->email,
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
$this->actingAs($this->user, 'web-guard')
|
||||||
|
->json('GET', '/user/logout');
|
||||||
|
|
||||||
|
$this->travel(1)->minute();
|
||||||
|
|
||||||
|
$this->json('POST', '/user/login', [
|
||||||
|
'email' => $this->user->email,
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
], [
|
||||||
|
'HTTP_USER_AGENT' => 'NotSymfony'
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
Notification::assertSentTo($this->user, SignedInWithNewDevice::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_login_does_not_send_new_device_notification()
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$this->user['preferences->notifyOnNewAuthDevice'] = 0;
|
||||||
|
$this->user->save();
|
||||||
|
|
||||||
|
$this->json('POST', '/user/login', [
|
||||||
|
'email' => $this->user->email,
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
$this->actingAs($this->user, 'web-guard')
|
||||||
|
->json('GET', '/user/logout');
|
||||||
|
|
||||||
|
$this->travel(1)->minute();
|
||||||
|
|
||||||
|
$this->json('POST', '/user/login', [
|
||||||
|
'email' => $this->user->email,
|
||||||
|
'password' => self::PASSWORD,
|
||||||
|
], [
|
||||||
|
'HTTP_USER_AGENT' => 'NotSymfony'
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
Notification::assertNothingSentTo($this->user);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*/
|
*/
|
||||||
@ -164,6 +228,39 @@ class LoginTest extends FeatureTestCase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_login_with_invalid_credentials_send_failed_login_notification()
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$this->json('POST', '/user/login', [
|
||||||
|
'email' => $this->user->email,
|
||||||
|
'password' => self::WRONG_PASSWORD,
|
||||||
|
])->assertStatus(401);
|
||||||
|
|
||||||
|
Notification::assertSentTo($this->user, FailedLogin::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_login_with_invalid_credentials_does_not_send_new_device_notification()
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$this->user['preferences->notifyOnFailedLogin'] = 0;
|
||||||
|
$this->user->save();
|
||||||
|
|
||||||
|
$this->json('POST', '/user/login', [
|
||||||
|
'email' => $this->user->email,
|
||||||
|
'password' => self::WRONG_PASSWORD,
|
||||||
|
])->assertStatus(401);
|
||||||
|
|
||||||
|
Notification::assertNothingSentTo($this->user);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Listeners\Authentication;
|
||||||
|
|
||||||
|
use App\Listeners\Authentication\FailedLoginListener;
|
||||||
|
use Illuminate\Auth\Events\Failed;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FailedLoginListenerTest test class
|
||||||
|
*/
|
||||||
|
#[CoversClass(FailedLoginListener::class)]
|
||||||
|
class FailedLoginListenerTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_FailedLoginListener_listen_to_Failed_event()
|
||||||
|
{
|
||||||
|
Event::fake();
|
||||||
|
|
||||||
|
Event::assertListening(
|
||||||
|
Failed::class,
|
||||||
|
FailedLoginListener::class
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
32
tests/Unit/Listeners/Authentication/LoginListenerTest.php
Normal file
32
tests/Unit/Listeners/Authentication/LoginListenerTest.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Listeners\Authentication;
|
||||||
|
|
||||||
|
use App\Listeners\Authentication\LoginListener;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Events\Login;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Mockery;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LoginListenerTest test class
|
||||||
|
*/
|
||||||
|
#[CoversClass(LoginListener::class)]
|
||||||
|
class LoginListenerTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_LoginListener_listen_to_Login_event()
|
||||||
|
{
|
||||||
|
Event::fake();
|
||||||
|
|
||||||
|
Event::assertListening(
|
||||||
|
Login::class,
|
||||||
|
LoginListener::class
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
29
tests/Unit/Listeners/Authentication/LogoutListenerTest.php
Normal file
29
tests/Unit/Listeners/Authentication/LogoutListenerTest.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Listeners\Authentication;
|
||||||
|
|
||||||
|
use App\Listeners\Authentication\LogoutListener;
|
||||||
|
use Illuminate\Auth\Events\Logout;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LogoutListenerTest test class
|
||||||
|
*/
|
||||||
|
#[CoversClass(LogoutListener::class)]
|
||||||
|
class LogoutListenerTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_LogoutListener_listen_to_Logout_event()
|
||||||
|
{
|
||||||
|
Event::fake();
|
||||||
|
|
||||||
|
Event::assertListening(
|
||||||
|
Logout::class,
|
||||||
|
LogoutListener::class
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Listeners\Authentication;
|
||||||
|
|
||||||
|
use App\Events\VisitedByProxyUser;
|
||||||
|
use App\Listeners\Authentication\VisitedByProxyUserListener;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VisitedByProxyUserListenerTest test class
|
||||||
|
*/
|
||||||
|
#[CoversClass(VisitedByProxyUserListener::class)]
|
||||||
|
class VisitedByProxyUserListenerTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function test_VisitedByProxyUserListener_listen_to_VisitedByProxyUser_event()
|
||||||
|
{
|
||||||
|
Event::fake();
|
||||||
|
|
||||||
|
Event::assertListening(
|
||||||
|
VisitedByProxyUser::class,
|
||||||
|
VisitedByProxyUserListener::class
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user