first commit
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Filters\UserKeywordSearch;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('matches username', function () {
|
||||
User::factory()->create(['username' => 'alice', 'email' => 'a@a.com']);
|
||||
User::factory()->create(['username' => 'bob', 'email' => 'b@b.com']);
|
||||
|
||||
$results = applyFilter('alic')->get();
|
||||
|
||||
$this->assertCount(1, $results);
|
||||
$this->assertSame('alice', $results->first()->username);
|
||||
});
|
||||
|
||||
test('matches email', function () {
|
||||
User::factory()->create(['email' => 'alice@example.com']);
|
||||
User::factory()->create(['email' => 'bob@example.com']);
|
||||
|
||||
$results = applyFilter('alice@')->get();
|
||||
|
||||
$this->assertCount(1, $results);
|
||||
});
|
||||
|
||||
test('matches first name and last name', function () {
|
||||
User::factory()->create(['first_name' => 'Alice', 'last_name' => 'Cooper']);
|
||||
User::factory()->create(['first_name' => 'Bob', 'last_name' => 'Dylan']);
|
||||
|
||||
$byFirst = applyFilter('Alic')->get();
|
||||
$byLast = applyFilter('Coop')->get();
|
||||
|
||||
$this->assertCount(1, $byFirst);
|
||||
$this->assertCount(1, $byLast);
|
||||
$this->assertSame('Alice', $byFirst->first()->first_name);
|
||||
$this->assertSame('Cooper', $byLast->first()->last_name);
|
||||
});
|
||||
|
||||
test('returns empty when no match', function () {
|
||||
User::factory()->create(['username' => 'alice']);
|
||||
|
||||
$results = applyFilter('xyzabc')->get();
|
||||
|
||||
$this->assertCount(0, $results);
|
||||
});
|
||||
|
||||
function applyFilter(string $search)
|
||||
{
|
||||
$query = User::query();
|
||||
|
||||
(new UserKeywordSearch)($query, $search);
|
||||
|
||||
return $query;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\CheckIfBanned;
|
||||
use App\Models\User;
|
||||
use App\Support\Enum\UserStatus;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('guest passes through', function () {
|
||||
$request = Request::create('/');
|
||||
|
||||
$response = (new CheckIfBanned)->handle($request, fn () => 'ok');
|
||||
|
||||
$this->assertSame('ok', $response);
|
||||
});
|
||||
|
||||
test('active user passes through', function () {
|
||||
$user = User::factory()->create(['status' => UserStatus::ACTIVE]);
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->setUserResolver(fn () => $user);
|
||||
|
||||
$response = (new CheckIfBanned)->handle($request, fn () => 'ok');
|
||||
|
||||
$this->assertSame('ok', $response);
|
||||
});
|
||||
|
||||
test('banned user is blocked', function () {
|
||||
$user = User::factory()->create(['status' => UserStatus::BANNED]);
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->setUserResolver(fn () => $user);
|
||||
|
||||
$this->expectException(HttpException::class);
|
||||
|
||||
(new CheckIfBanned)->handle($request, fn () => 'ok');
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\CheckPermissions;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Facades\Tests\Setup\UserFactory;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('guest is blocked', function () {
|
||||
$request = Request::create('/');
|
||||
|
||||
$this->expectException(HttpException::class);
|
||||
|
||||
permissionMiddleware()->handle($request, fn () => 'ok', 'users.manage');
|
||||
});
|
||||
|
||||
test('user without permission is blocked', function () {
|
||||
$user = UserFactory::user()->create();
|
||||
$this->be($user);
|
||||
|
||||
$this->expectException(HttpException::class);
|
||||
|
||||
permissionMiddleware()->handle(permissionRequestFor($user), fn () => 'ok', 'users.manage');
|
||||
});
|
||||
|
||||
test('user with permission passes', function () {
|
||||
$user = UserFactory::withPermissions('users.manage')->create();
|
||||
$this->be($user);
|
||||
|
||||
$response = permissionMiddleware()->handle(permissionRequestFor($user), fn () => 'ok', 'users.manage');
|
||||
|
||||
$this->assertSame('ok', $response);
|
||||
});
|
||||
|
||||
test('multiple permissions separated by pipe', function () {
|
||||
$role = Role::factory()->create();
|
||||
$role->attachPermission(Permission::where('name', 'users.manage')->first());
|
||||
$role->attachPermission(Permission::where('name', 'roles.manage')->first());
|
||||
|
||||
$user = User::factory()->create(['role_id' => $role->id]);
|
||||
$this->be($user);
|
||||
|
||||
$response = permissionMiddleware()->handle(permissionRequestFor($user), fn () => 'ok', 'users.manage|roles.manage');
|
||||
|
||||
$this->assertSame('ok', $response);
|
||||
});
|
||||
|
||||
function permissionRequestFor($user): Request
|
||||
{
|
||||
$request = Request::create('/');
|
||||
$request->setUserResolver(fn () => $user);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
function permissionMiddleware(): CheckPermissions
|
||||
{
|
||||
return new CheckPermissions(auth()->guard());
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\CheckRole;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('guest is blocked', function () {
|
||||
$this->expectException(HttpException::class);
|
||||
|
||||
roleMiddleware()->handle(Request::create('/'), fn () => 'ok', 'Admin');
|
||||
});
|
||||
|
||||
test('user with matching role passes', function () {
|
||||
$role = Role::factory()->create(['name' => 'Editor']);
|
||||
$user = User::factory()->create(['role_id' => $role->id]);
|
||||
$this->be($user);
|
||||
|
||||
$response = roleMiddleware()->handle(roleRequestFor($user), fn () => 'ok', 'Editor');
|
||||
|
||||
$this->assertSame('ok', $response);
|
||||
});
|
||||
|
||||
test('user with wrong role is blocked', function () {
|
||||
$role = Role::factory()->create(['name' => 'Editor']);
|
||||
$user = User::factory()->create(['role_id' => $role->id]);
|
||||
$this->be($user);
|
||||
|
||||
$this->expectException(HttpException::class);
|
||||
|
||||
roleMiddleware()->handle(roleRequestFor($user), fn () => 'ok', 'Admin');
|
||||
});
|
||||
|
||||
function roleRequestFor($user): Request
|
||||
{
|
||||
$request = Request::create('/');
|
||||
$request->setUserResolver(fn () => $user);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
function roleMiddleware(): CheckRole
|
||||
{
|
||||
return new CheckRole(auth()->guard());
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\EnsureUserIsApproved;
|
||||
use App\Models\User;
|
||||
use App\Support\Enum\UserStatus;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('guest passes through', function () {
|
||||
$response = (new EnsureUserIsApproved)->handle(Request::create('/'), fn () => 'ok');
|
||||
|
||||
$this->assertSame('ok', $response);
|
||||
});
|
||||
|
||||
test('active user passes through', function () {
|
||||
$user = User::factory()->create(['status' => UserStatus::ACTIVE]);
|
||||
$request = Request::create('/');
|
||||
$request->setUserResolver(fn () => $user);
|
||||
|
||||
$response = (new EnsureUserIsApproved)->handle($request, fn () => 'ok');
|
||||
|
||||
$this->assertSame('ok', $response);
|
||||
});
|
||||
|
||||
test('waiting user redirects to approval notice for web', function () {
|
||||
Route::get('/approval-notice', fn () => 'notice')->name('approval.notice');
|
||||
|
||||
$user = User::factory()->create(['status' => UserStatus::WAITING_APPROVAL]);
|
||||
$request = Request::create('/');
|
||||
$request->setUserResolver(fn () => $user);
|
||||
|
||||
$response = (new EnsureUserIsApproved)->handle($request, fn () => 'ok');
|
||||
|
||||
$this->assertSame(302, $response->getStatusCode());
|
||||
$this->assertStringContainsString('/approval-notice', $response->headers->get('Location'));
|
||||
});
|
||||
|
||||
test('waiting user aborts for json request', function () {
|
||||
$user = User::factory()->create(['status' => UserStatus::WAITING_APPROVAL]);
|
||||
$request = Request::create('/');
|
||||
$request->headers->set('Accept', 'application/json');
|
||||
$request->setUserResolver(fn () => $user);
|
||||
|
||||
$this->expectException(HttpException::class);
|
||||
|
||||
(new EnsureUserIsApproved)->handle($request, fn () => 'ok');
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\PasswordResetEnabled;
|
||||
use App\Http\Middleware\RegistrationEnabled;
|
||||
use App\Http\Middleware\TwoFactorEnabled;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Tests\UpdatesSettings;
|
||||
|
||||
uses(UpdatesSettings::class);
|
||||
|
||||
test('registration enabled aborts when disabled', function () {
|
||||
$this->setSettings(['reg_enabled' => false]);
|
||||
|
||||
$this->expectException(NotFoundHttpException::class);
|
||||
|
||||
(new RegistrationEnabled)->handle(Request::create('/'), fn () => 'ok');
|
||||
});
|
||||
|
||||
test('registration enabled passes when enabled', function () {
|
||||
$this->setSettings(['reg_enabled' => true]);
|
||||
|
||||
$this->assertSame('ok', (new RegistrationEnabled)->handle(Request::create('/'), fn () => 'ok'));
|
||||
});
|
||||
|
||||
test('password reset aborts when disabled', function () {
|
||||
$this->setSettings(['forgot_password' => false]);
|
||||
|
||||
$this->expectException(NotFoundHttpException::class);
|
||||
|
||||
(new PasswordResetEnabled)->handle(Request::create('/'), fn () => 'ok');
|
||||
});
|
||||
|
||||
test('password reset passes when enabled', function () {
|
||||
$this->setSettings(['forgot_password' => true]);
|
||||
|
||||
$this->assertSame('ok', (new PasswordResetEnabled)->handle(Request::create('/'), fn () => 'ok'));
|
||||
});
|
||||
|
||||
test('two factor aborts when disabled', function () {
|
||||
$this->setSettings(['2fa.enabled' => false]);
|
||||
|
||||
$this->expectException(NotFoundHttpException::class);
|
||||
|
||||
(new TwoFactorEnabled)->handle(Request::create('/'), fn () => 'ok');
|
||||
});
|
||||
|
||||
test('two factor passes when enabled', function () {
|
||||
$this->setSettings(['2fa.enabled' => true]);
|
||||
|
||||
$this->assertSame('ok', (new TwoFactorEnabled)->handle(Request::create('/'), fn () => 'ok'));
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\SocialLogin;
|
||||
|
||||
test('allows configured provider', function () {
|
||||
config(['auth.social.providers' => ['github', 'google']]);
|
||||
|
||||
$response = $this->get('auth/github/login');
|
||||
|
||||
// Either the controller handles the request or another middleware
|
||||
// intervenes — but the SocialLogin middleware itself should not 404.
|
||||
$this->assertNotEquals(404, $response->getStatusCode());
|
||||
});
|
||||
|
||||
test('middleware throws when provider not in config', function () {
|
||||
config(['auth.social.providers' => ['github']]);
|
||||
|
||||
// Build a real route so $request->route()->parameter('provider') resolves.
|
||||
Route::middleware(SocialLogin::class)
|
||||
->get('/test-social/{provider}', fn ($provider) => 'ok');
|
||||
|
||||
$response = $this->get('/test-social/unknown');
|
||||
|
||||
$response->assertNotFound();
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\UseApiGuard;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
test('middleware switches default guard to sanctum and sets accept header', function () {
|
||||
$request = Request::create('/');
|
||||
|
||||
$response = (new UseApiGuard(app('auth')))->handle($request, fn ($req) => $req);
|
||||
|
||||
$this->assertSame('application/json', $response->headers->get('Accept'));
|
||||
$this->assertSame('sanctum', auth()->getDefaultDriver());
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Resources\CountryResource;
|
||||
use App\Models\Country;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('resource has expected keys', function () {
|
||||
$country = Country::first();
|
||||
|
||||
$array = (new CountryResource($country))->toArray(new Request);
|
||||
|
||||
$this->assertSame([
|
||||
'id', 'name', 'full_name', 'capital', 'citizenship', 'country_code',
|
||||
'currency', 'currency_code', 'currency_sub_unit', 'currency_symbol',
|
||||
'iso_3166_2', 'iso_3166_3', 'region_code', 'sub_region_code',
|
||||
'eea', 'calling_code', 'flag',
|
||||
], array_keys($array));
|
||||
});
|
||||
|
||||
test('flag resolves to url when set', function () {
|
||||
$country = Country::first();
|
||||
$country->flag = 'UK.png';
|
||||
|
||||
$array = (new CountryResource($country))->toArray(new Request);
|
||||
|
||||
$this->assertStringContainsString('flags/UK.png', $array['flag']);
|
||||
});
|
||||
|
||||
test('flag is null when missing', function () {
|
||||
$country = Country::first();
|
||||
$country->flag = null;
|
||||
|
||||
$array = (new CountryResource($country))->toArray(new Request);
|
||||
|
||||
$this->assertNull($array['flag']);
|
||||
});
|
||||
|
||||
test('numeric casts', function () {
|
||||
$country = Country::first();
|
||||
|
||||
$array = (new CountryResource($country))->toArray(new Request);
|
||||
|
||||
$this->assertIsInt($array['id']);
|
||||
$this->assertIsInt($array['country_code']);
|
||||
$this->assertIsInt($array['region_code']);
|
||||
$this->assertIsBool($array['eea']);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Resources\PermissionResource;
|
||||
use App\Models\Permission;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('resource has expected keys', function () {
|
||||
$permission = Permission::factory()->create();
|
||||
|
||||
$array = (new PermissionResource($permission))->toArray(new Request);
|
||||
|
||||
$this->assertSame([
|
||||
'id', 'name', 'display_name', 'description', 'removable',
|
||||
'updated_at', 'created_at',
|
||||
], array_keys($array));
|
||||
});
|
||||
|
||||
test('id is int removable is bool', function () {
|
||||
$permission = Permission::factory()->create(['removable' => 1]);
|
||||
|
||||
$array = (new PermissionResource($permission))->toArray(new Request);
|
||||
|
||||
$this->assertIsInt($array['id']);
|
||||
$this->assertTrue($array['removable']);
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Resources\RoleResource;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Role;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
use Spatie\QueryBuilder\AllowedInclude;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('resource includes permissions when loaded', function () {
|
||||
$role = Role::factory()->create();
|
||||
$perm = Permission::factory()->create();
|
||||
$role->attachPermission($perm);
|
||||
$role->load('permissions');
|
||||
|
||||
$array = (new RoleResource($role))->toArray(new Request);
|
||||
|
||||
$this->assertNotEmpty($array['permissions']);
|
||||
});
|
||||
|
||||
test('users count is int when present', function () {
|
||||
$role = Role::factory()->create();
|
||||
$role->users_count = '5';
|
||||
|
||||
$array = (new RoleResource($role))->toArray(new Request);
|
||||
|
||||
$this->assertSame(5, $array['users_count']);
|
||||
});
|
||||
|
||||
test('users count is null when not set', function () {
|
||||
$role = Role::factory()->create();
|
||||
|
||||
$array = (new RoleResource($role))->toArray(new Request);
|
||||
|
||||
$this->assertNull($array['users_count']);
|
||||
});
|
||||
|
||||
test('allowed includes returns permissions and users count', function () {
|
||||
$includes = RoleResource::allowedIncludes();
|
||||
|
||||
$this->assertContains('permissions', $includes);
|
||||
$this->assertCount(2, $includes);
|
||||
$this->assertTrue(collect($includes)->contains(fn ($i) => $i instanceof AllowedInclude || $i === 'permissions'));
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Resources\SessionResource;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
test('resource has expected keys', function () {
|
||||
$session = (object) [
|
||||
'id' => 'abc123',
|
||||
'user_id' => 42,
|
||||
'ip_address' => '127.0.0.1',
|
||||
'user_agent' => 'PHPUnit',
|
||||
'browser' => 'Chrome',
|
||||
'platform' => 'macOS',
|
||||
'device' => 'Desktop',
|
||||
'last_activity' => '2026-01-01 00:00:00',
|
||||
];
|
||||
|
||||
$array = (new SessionResource($session))->toArray(new Request);
|
||||
|
||||
$this->assertSame([
|
||||
'id', 'user_id', 'ip_address', 'user_agent',
|
||||
'browser', 'platform', 'device', 'last_activity',
|
||||
], array_keys($array));
|
||||
|
||||
$this->assertSame(42, $array['user_id']);
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Resources\UserResource;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Facades\Tests\Setup\UserFactory;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('resource has expected keys', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$array = (new UserResource($user))->toArray(fakeRequest($user));
|
||||
|
||||
$expected = [
|
||||
'id', 'first_name', 'last_name', 'username', 'email', 'phone', 'avatar',
|
||||
'address', 'country_id', 'role_id', 'status', 'birthday', 'last_login',
|
||||
'two_factor_country_code', 'two_factor_phone', 'two_factor_options',
|
||||
'email_verified_at', 'created_at', 'updated_at', 'country',
|
||||
];
|
||||
|
||||
foreach ($expected as $key) {
|
||||
$this->assertArrayHasKey($key, $array, "missing key: $key");
|
||||
}
|
||||
});
|
||||
|
||||
test('role is hidden without permission', function () {
|
||||
$role = Role::factory()->create();
|
||||
$user = User::factory()->create(['role_id' => $role->id]);
|
||||
$user->load('role');
|
||||
|
||||
$serialized = serializeResource($user);
|
||||
|
||||
$this->assertArrayNotHasKey('role', $serialized);
|
||||
});
|
||||
|
||||
test('role is visible with permission', function () {
|
||||
$admin = UserFactory::withPermissions('roles.manage')->create();
|
||||
$admin->load('role');
|
||||
|
||||
$serialized = serializeResource($admin);
|
||||
|
||||
$this->assertArrayHasKey('role', $serialized);
|
||||
});
|
||||
|
||||
test('allowed includes', function () {
|
||||
$this->assertSame(['role', 'country'], UserResource::allowedIncludes());
|
||||
});
|
||||
|
||||
function fakeRequest(User $user): Request
|
||||
{
|
||||
$request = Request::create('/');
|
||||
$request->setUserResolver(fn () => $user);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the resource through JSON to filter out MissingValue placeholders.
|
||||
*/
|
||||
function serializeResource(User $user): array
|
||||
{
|
||||
return json_decode(
|
||||
(new UserResource($user))->toResponse(fakeRequest($user))->getContent(),
|
||||
true,
|
||||
)['data'];
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use App\Events\User\Approved;
|
||||
use App\Listeners\Approve\SendApprovedNotification;
|
||||
use App\Mail\UserApproved;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Tests\UpdatesSettings;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
uses(UpdatesSettings::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('mail is sent when approval enabled', function () {
|
||||
$this->setSettings(['approval.enabled' => true]);
|
||||
|
||||
Mail::fake();
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
(new SendApprovedNotification)->handle(new Approved($user));
|
||||
|
||||
Mail::assertQueued(UserApproved::class, fn ($mail) => $mail->hasTo($user->email));
|
||||
});
|
||||
|
||||
test('mail is not sent when approval disabled', function () {
|
||||
$this->setSettings(['approval.enabled' => false]);
|
||||
|
||||
Mail::fake();
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
(new SendApprovedNotification)->handle(new Approved($user));
|
||||
|
||||
Mail::assertNothingQueued();
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
use App\Listeners\Approve\SendWaitingApprovalNotification;
|
||||
use App\Mail\WaitingApproval;
|
||||
use App\Repositories\User\UserRepository;
|
||||
use Facades\Tests\Setup\UserFactory;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Tests\UpdatesSettings;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
uses(UpdatesSettings::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('mail is sent to each admin when approval enabled', function () {
|
||||
$this->setSettings(['approval.enabled' => true]);
|
||||
|
||||
Mail::fake();
|
||||
|
||||
$admin1 = UserFactory::admin()->create();
|
||||
$admin2 = UserFactory::admin()->create();
|
||||
$regular = UserFactory::user()->create();
|
||||
|
||||
$listener = new SendWaitingApprovalNotification(app(UserRepository::class));
|
||||
$listener->handle(new Verified($regular));
|
||||
|
||||
Mail::assertQueued(WaitingApproval::class, fn ($mail) => $mail->hasTo($admin1->email));
|
||||
Mail::assertQueued(WaitingApproval::class, fn ($mail) => $mail->hasTo($admin2->email));
|
||||
Mail::assertNotQueued(WaitingApproval::class, fn ($mail) => $mail->hasTo($regular->email));
|
||||
});
|
||||
|
||||
test('mail is not sent when approval disabled', function () {
|
||||
$this->setSettings(['approval.enabled' => false]);
|
||||
|
||||
Mail::fake();
|
||||
|
||||
UserFactory::admin()->create();
|
||||
$user = UserFactory::user()->create();
|
||||
|
||||
$listener = new SendWaitingApprovalNotification(app(UserRepository::class));
|
||||
$listener->handle(new Verified($user));
|
||||
|
||||
Mail::assertNothingQueued();
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use App\Events\User\LoggedIn;
|
||||
use App\Listeners\Login\UpdateLastLoginTimestamp;
|
||||
use App\Models\User;
|
||||
use App\Repositories\User\UserRepository;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('last login is set to now for authenticated user', function () {
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$user = User::factory()->create(['last_login' => null]);
|
||||
$this->be($user);
|
||||
|
||||
$listener = new UpdateLastLoginTimestamp(app(UserRepository::class), auth()->guard());
|
||||
$listener->handle(new LoggedIn);
|
||||
|
||||
$this->assertEquals(Carbon::now()->timestamp, $user->fresh()->last_login->timestamp);
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
use App\Listeners\Registration\SendSignUpNotification;
|
||||
use App\Mail\UserRegistered;
|
||||
use App\Repositories\User\UserRepository;
|
||||
use Facades\Tests\Setup\UserFactory;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Tests\UpdatesSettings;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
uses(UpdatesSettings::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('admins receive notification when signup emails enabled', function () {
|
||||
$this->setSettings(['notifications_signup_email' => true]);
|
||||
|
||||
Mail::fake();
|
||||
|
||||
$admin = UserFactory::admin()->create();
|
||||
$newUser = UserFactory::user()->create();
|
||||
|
||||
$listener = new SendSignUpNotification(app(UserRepository::class));
|
||||
$listener->handle(new Registered($newUser));
|
||||
|
||||
Mail::assertQueued(UserRegistered::class, fn ($mail) => $mail->hasTo($admin->email));
|
||||
});
|
||||
|
||||
test('no mail when signup emails disabled', function () {
|
||||
$this->setSettings(['notifications_signup_email' => false]);
|
||||
|
||||
Mail::fake();
|
||||
|
||||
UserFactory::admin()->create();
|
||||
$newUser = UserFactory::user()->create();
|
||||
|
||||
$listener = new SendSignUpNotification(app(UserRepository::class));
|
||||
$listener->handle(new Registered($newUser));
|
||||
|
||||
Mail::assertNothingQueued();
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use App\Listeners\Users\ActivateUser;
|
||||
use App\Models\User;
|
||||
use App\Repositories\User\UserRepository;
|
||||
use App\Support\Enum\UserStatus;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\UpdatesSettings;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
uses(UpdatesSettings::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('user becomes active when approval disabled', function () {
|
||||
$this->setSettings(['approval.enabled' => false]);
|
||||
|
||||
$user = User::factory()->create(['status' => UserStatus::UNCONFIRMED]);
|
||||
|
||||
$listener = new ActivateUser(app(UserRepository::class));
|
||||
$listener->handle(new Verified($user));
|
||||
|
||||
$this->assertSame(UserStatus::ACTIVE, $user->fresh()->status);
|
||||
});
|
||||
|
||||
test('user waits for approval when approval enabled', function () {
|
||||
$this->setSettings(['approval.enabled' => true]);
|
||||
|
||||
$user = User::factory()->create(['status' => UserStatus::UNCONFIRMED]);
|
||||
|
||||
$listener = new ActivateUser(app(UserRepository::class));
|
||||
$listener->handle(new Verified($user));
|
||||
|
||||
$this->assertSame(UserStatus::WAITING_APPROVAL, $user->fresh()->status);
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use App\Events\User\Banned;
|
||||
use App\Listeners\Users\InvalidateSessions;
|
||||
use App\Models\User;
|
||||
use App\Repositories\Session\SessionRepository;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('all user sessions are invalidated', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
DB::table('sessions')->insert([
|
||||
'id' => Str::random(),
|
||||
'user_id' => $user->id,
|
||||
'ip_address' => fake()->ipv4,
|
||||
'user_agent' => fake()->userAgent,
|
||||
'payload' => 'foo',
|
||||
'last_activity' => Carbon::now()->timestamp,
|
||||
]);
|
||||
|
||||
$listener = new InvalidateSessions(app(SessionRepository::class));
|
||||
$listener->handle(new Banned($user));
|
||||
|
||||
$this->assertDatabaseMissing('sessions', ['user_id' => $user->id]);
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use App\Mail\ResetPassword;
|
||||
|
||||
test('subject includes app name', function () {
|
||||
Setting::set('app_name', 'TestApp');
|
||||
|
||||
$mailable = new ResetPassword('the-token');
|
||||
$mailable->build();
|
||||
|
||||
$mailable->assertHasSubject('[TestApp] '.__('Password Reset Request'));
|
||||
});
|
||||
|
||||
test('uses reset password markdown view', function () {
|
||||
$mailable = new ResetPassword('the-token');
|
||||
|
||||
$mailable->assertSeeInText('reset');
|
||||
});
|
||||
|
||||
test('token is publicly accessible', function () {
|
||||
$mailable = new ResetPassword('the-token');
|
||||
|
||||
$this->assertSame('the-token', $mailable->token);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
use App\Mail\UserApproved;
|
||||
|
||||
test('subject includes app name', function () {
|
||||
Setting::set('app_name', 'TestApp');
|
||||
|
||||
$mailable = new UserApproved;
|
||||
$mailable->build();
|
||||
|
||||
$mailable->assertHasSubject('[TestApp] '.__('Your Account Has Been Approved'));
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use App\Mail\UserRegistered;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('subject includes app name', function () {
|
||||
Setting::set('app_name', 'TestApp');
|
||||
|
||||
$user = User::factory()->create();
|
||||
$mailable = new UserRegistered($user);
|
||||
$mailable->build();
|
||||
|
||||
$mailable->assertHasSubject('[TestApp] '.__('New User Registration'));
|
||||
});
|
||||
|
||||
test('user is publicly accessible', function () {
|
||||
$user = User::factory()->create();
|
||||
$mailable = new UserRegistered($user);
|
||||
|
||||
$this->assertTrue($user->is($mailable->user));
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use App\Mail\WaitingApproval;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('subject includes app name', function () {
|
||||
Setting::set('app_name', 'TestApp');
|
||||
|
||||
$user = User::factory()->create();
|
||||
$mailable = new WaitingApproval($user);
|
||||
$mailable->build();
|
||||
|
||||
$mailable->assertHasSubject('[TestApp] '.__('User Approval'));
|
||||
});
|
||||
|
||||
test('user is publicly accessible', function () {
|
||||
$user = User::factory()->create();
|
||||
$mailable = new WaitingApproval($user);
|
||||
|
||||
$this->assertTrue($user->is($mailable->user));
|
||||
});
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use App\Support\Sidebar\Item;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Vanguard\Plugins\Contracts\Slot;
|
||||
use Vanguard\Plugins\Plugin;
|
||||
use Vanguard\Plugins\Vanguard;
|
||||
use Vanguard\Plugins\Widget;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
afterEach(function () {
|
||||
Vanguard::$slots = [];
|
||||
Vanguard::$widgets = [];
|
||||
});
|
||||
|
||||
test('slot handlers are registered per position', function () {
|
||||
Vanguard::$slots = [];
|
||||
|
||||
$this->assertFalse(Vanguard::hasSlot('navbar:items'));
|
||||
|
||||
Vanguard::slot('navbar:items', FakeSlot::class);
|
||||
|
||||
$this->assertTrue(Vanguard::hasSlot('navbar:items'));
|
||||
$this->assertSame([FakeSlot::class], Vanguard::slotHandlers('navbar:items'));
|
||||
});
|
||||
|
||||
test('multiple handlers may share a position', function () {
|
||||
Vanguard::$slots = [];
|
||||
|
||||
Vanguard::slot('navbar:items', FakeSlot::class);
|
||||
Vanguard::slot('navbar:items', OtherFakeSlot::class);
|
||||
|
||||
$this->assertCount(2, Vanguard::slotHandlers('navbar:items'));
|
||||
});
|
||||
|
||||
test('registering the same handler twice keeps a single entry', function () {
|
||||
Vanguard::$slots = [];
|
||||
|
||||
// Handlers are declared as class names, so a repeat only ever means the
|
||||
// service provider booted again: under Octane, or between tests, where the
|
||||
// static registry outlives the container. Appending would render the slot
|
||||
// once per boot.
|
||||
Vanguard::slot('navbar:items', FakeSlot::class);
|
||||
Vanguard::slot('navbar:items', FakeSlot::class);
|
||||
|
||||
$this->assertSame([FakeSlot::class], Vanguard::slotHandlers('navbar:items'));
|
||||
});
|
||||
|
||||
test('registering the same widget twice keeps a single entry', function () {
|
||||
Vanguard::$widgets = [];
|
||||
|
||||
Vanguard::widgets([new FakeWidget]);
|
||||
Vanguard::widgets([new FakeWidget]);
|
||||
|
||||
$this->assertCount(1, Vanguard::availableWidgets(User::factory()->create()));
|
||||
});
|
||||
|
||||
test('resolving a slot returns component and props', function () {
|
||||
Vanguard::$slots = [];
|
||||
|
||||
Vanguard::slot('user:show', FakeSlot::class);
|
||||
|
||||
$this->assertSame([
|
||||
[
|
||||
'component' => 'fake::Component',
|
||||
'props' => ['greeting' => 'hello world'],
|
||||
],
|
||||
], Vanguard::resolveSlot('user:show', ['greeting' => 'hello world']));
|
||||
});
|
||||
|
||||
test('resolving an unregistered slot returns an empty array', function () {
|
||||
Vanguard::$slots = [];
|
||||
|
||||
$this->assertSame([], Vanguard::resolveSlot('nope:nothing'));
|
||||
});
|
||||
|
||||
test('widget serialises to component props and width', function () {
|
||||
$widget = new FakeWidget;
|
||||
|
||||
$this->assertSame([
|
||||
'component' => 'fake::Widget',
|
||||
'props' => ['count' => 7],
|
||||
'width' => '4',
|
||||
], $widget->toArray());
|
||||
});
|
||||
|
||||
test('widget without permissions authorizes everyone', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->assertTrue((new FakeWidget)->authorize($user));
|
||||
});
|
||||
|
||||
test('widget permissions may be a closure', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$widget = (new FakeWidget)->permissions(fn () => false);
|
||||
|
||||
$this->assertFalse($widget->authorize($user));
|
||||
});
|
||||
|
||||
test('plugin declares no assets by default', function () {
|
||||
$plugin = new FakePlugin($this->app);
|
||||
|
||||
$this->assertSame([], $plugin->assets());
|
||||
});
|
||||
|
||||
test('registry collects assets from plugins that declare them', function () {
|
||||
Vanguard::$plugins = [
|
||||
FakePlugin::class => new FakePlugin($this->app),
|
||||
FakePluginWithAssets::class => new FakePluginWithAssets($this->app),
|
||||
];
|
||||
|
||||
$this->assertSame(
|
||||
[['js' => '/vendor/plugins/fake/plugin.js']],
|
||||
Vanguard::assets()
|
||||
);
|
||||
});
|
||||
|
||||
class FakeSlot implements Slot
|
||||
{
|
||||
public function component(): string
|
||||
{
|
||||
return 'fake::Component';
|
||||
}
|
||||
|
||||
public function data(array $context = []): array
|
||||
{
|
||||
return ['greeting' => $context['greeting'] ?? null];
|
||||
}
|
||||
}
|
||||
|
||||
class OtherFakeSlot extends FakeSlot {}
|
||||
|
||||
class FakeWidget extends Widget
|
||||
{
|
||||
public ?string $width = '4';
|
||||
|
||||
public function component(): string
|
||||
{
|
||||
return 'fake::Widget';
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return ['count' => 7];
|
||||
}
|
||||
}
|
||||
|
||||
class FakePlugin extends Plugin
|
||||
{
|
||||
public function sidebar(): ?Item
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class FakePluginWithAssets extends Plugin
|
||||
{
|
||||
public function assets(): array
|
||||
{
|
||||
return ['js' => '/vendor/plugins/fake/plugin.js'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
use App\Presenters\Traits\Presentable;
|
||||
use App\Presenters\UserPresenter;
|
||||
use App\Support\Enum\UserStatus;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->user = new PresentableUser;
|
||||
});
|
||||
|
||||
test('test test name', function () {
|
||||
$this->assertEquals('John Doe', $this->user->present()->name);
|
||||
});
|
||||
|
||||
test('test name or email', function () {
|
||||
$this->assertEquals('John Doe', $this->user->present()->nameOrEmail);
|
||||
|
||||
$this->user->first_name = '';
|
||||
$this->assertEquals('Doe', $this->user->present()->nameOrEmail);
|
||||
|
||||
$this->user->last_name = '';
|
||||
$this->assertEquals('john.doe@test.com', $this->user->present()->nameOrEmail);
|
||||
});
|
||||
|
||||
test('test avatar', function () {
|
||||
$this->assertEquals(url('assets/img/profile.png'), $this->user->present()->avatar);
|
||||
|
||||
$this->user->avatar = '//www.gravatar.com/avatar';
|
||||
$this->assertEquals('//www.gravatar.com/avatar', $this->user->present()->avatar);
|
||||
|
||||
$this->user->avatar = 'http://somewebsite.com/avatar.png';
|
||||
$this->assertEquals('http://somewebsite.com/avatar.png', $this->user->present()->avatar);
|
||||
|
||||
$this->user->avatar = 'foo.png';
|
||||
$this->assertEquals(url('storage/upload/users/foo.png'), $this->user->present()->avatar);
|
||||
});
|
||||
|
||||
test('test birthday', function () {
|
||||
$this->assertEquals('N/A', $this->user->present()->birthday);
|
||||
|
||||
$this->user->birthday = Carbon::now()->subYears(20);
|
||||
$this->assertEquals($this->user->birthday->toDateString(), $this->user->present()->birthday);
|
||||
});
|
||||
|
||||
test('test full address', function () {
|
||||
$this->assertEquals('Some Country', $this->user->present()->fullAddress);
|
||||
|
||||
$this->user->address = 'Street 123';
|
||||
$this->assertEquals('Street 123, Some Country', $this->user->present()->fullAddress);
|
||||
|
||||
$this->user->country_id = null;
|
||||
$this->user->country = null;
|
||||
$this->assertEquals('Street 123', $this->user->present()->fullAddress);
|
||||
|
||||
$this->user->address = '';
|
||||
$this->assertEquals('N/A', $this->user->present()->fullAddress);
|
||||
});
|
||||
|
||||
test('test last login', function () {
|
||||
$this->assertEquals('N/A', $this->user->present()->lastLogin);
|
||||
|
||||
$date = Carbon::now()->subDay(1);
|
||||
$this->user->last_login = $date->copy();
|
||||
$this->assertEquals($date->diffForHumans(), $this->user->present()->lastLogin);
|
||||
});
|
||||
|
||||
test('test label class', function () {
|
||||
$this->assertEquals('success', $this->user->present()->labelClass);
|
||||
|
||||
$this->user->status = UserStatus::BANNED;
|
||||
$this->assertEquals('danger', $this->user->present()->labelClass);
|
||||
|
||||
$this->user->status = UserStatus::UNCONFIRMED;
|
||||
$this->assertEquals('warning', $this->user->present()->labelClass);
|
||||
});
|
||||
|
||||
class PresentableUser extends Model
|
||||
{
|
||||
use Presentable;
|
||||
|
||||
protected $presenter = UserPresenter::class;
|
||||
|
||||
public $first_name = 'John';
|
||||
|
||||
public $last_name = 'Doe';
|
||||
|
||||
public $email = 'john.doe@test.com';
|
||||
|
||||
public $avatar = null;
|
||||
|
||||
public $birthday = null;
|
||||
|
||||
public $country_id = 1;
|
||||
|
||||
public $country;
|
||||
|
||||
public $address = '';
|
||||
|
||||
public $last_login = null;
|
||||
|
||||
public $status = UserStatus::ACTIVE;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->country = new StubCountry;
|
||||
}
|
||||
}
|
||||
|
||||
class StubCountry
|
||||
{
|
||||
public $id = 1;
|
||||
|
||||
public $name = 'Some Country';
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Repositories\Country\EloquentCountry;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->repo = app(EloquentCountry::class);
|
||||
});
|
||||
|
||||
test('lists', function () {
|
||||
$countries = Country::factory()->times(8)->create();
|
||||
$countries = $countries->sortBy(function ($country) {
|
||||
return $country->name;
|
||||
})->pluck('name', 'id');
|
||||
|
||||
$this->assertEquals($countries->toArray(), $this->repo->lists()->toArray());
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
use App\Events\Permission\Created;
|
||||
use App\Events\Permission\Deleted;
|
||||
use App\Events\Permission\Updated;
|
||||
use App\Models\Permission;
|
||||
use App\Repositories\Permission\EloquentPermission;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->repo = app(EloquentPermission::class);
|
||||
});
|
||||
|
||||
test('all', function () {
|
||||
$permissions = Permission::factory()->times(4)->create();
|
||||
|
||||
$this->assertEquals($permissions->toArray(), $this->repo->all()->toArray());
|
||||
});
|
||||
|
||||
test('create permission', function () {
|
||||
Event::fake([
|
||||
Created::class,
|
||||
]);
|
||||
|
||||
$data = getPermissionStubData();
|
||||
|
||||
$perm = $this->repo->create($data);
|
||||
|
||||
$this->assertDatabaseHas('permissions', $data + ['id' => $perm->id]);
|
||||
|
||||
Event::assertDispatched(Created::class);
|
||||
});
|
||||
|
||||
test('update permission', function () {
|
||||
Event::fake([
|
||||
Updated::class,
|
||||
]);
|
||||
|
||||
Cache::put('foo', 'bar');
|
||||
|
||||
$data = getPermissionStubData();
|
||||
|
||||
$perm = Permission::factory()->create();
|
||||
|
||||
$this->repo->update($perm->id, $data);
|
||||
|
||||
$this->assertDatabaseHas('permissions', $data + ['id' => $perm->id])
|
||||
->assertNull(Cache::get('foo'));
|
||||
|
||||
Event::assertDispatched(Updated::class);
|
||||
});
|
||||
|
||||
test('delete permission', function () {
|
||||
Event::fake([
|
||||
Deleted::class,
|
||||
]);
|
||||
|
||||
Cache::put('foo', 'bar');
|
||||
|
||||
$perm = Permission::factory()->create();
|
||||
|
||||
$this->repo->delete($perm->id);
|
||||
|
||||
$this->assertDatabaseMissing('permissions', ['id' => $perm->id])
|
||||
->assertNull(Cache::get('foo'));
|
||||
|
||||
Event::assertDispatched(Deleted::class);
|
||||
});
|
||||
|
||||
function getPermissionStubData(): array
|
||||
{
|
||||
return [
|
||||
'name' => Str::random(5),
|
||||
'display_name' => Str::random(5),
|
||||
'description' => 'foo',
|
||||
'removable' => true,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
use App\Events\Role\Created;
|
||||
use App\Events\Role\Deleted;
|
||||
use App\Events\Role\Updated;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Role;
|
||||
use App\Repositories\Role\EloquentRole;
|
||||
use Facades\Tests\Setup\RoleFactory;
|
||||
use Facades\Tests\Setup\UserFactory;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->repo = app(EloquentRole::class);
|
||||
});
|
||||
|
||||
test('all', function () {
|
||||
$roles = Role::factory()->times(4)->create();
|
||||
|
||||
$this->assertEquals(
|
||||
$roles->toArray(),
|
||||
$this->repo->all()->toArray()
|
||||
);
|
||||
});
|
||||
|
||||
test('get all with users count', function () {
|
||||
$roleA = RoleFactory::create();
|
||||
$roleB = RoleFactory::create();
|
||||
$roleC = RoleFactory::create();
|
||||
|
||||
UserFactory::role($roleA)->create();
|
||||
UserFactory::role($roleA)->create();
|
||||
UserFactory::role($roleB)->create();
|
||||
|
||||
$roleA->users_count = 2;
|
||||
$roleB->users_count = 1;
|
||||
$roleC->users_count = 0;
|
||||
|
||||
$this->assertEquals(
|
||||
[$roleA->toArray(), $roleB->toArray(), $roleC->toArray()],
|
||||
$this->repo->getAllWithUsersCount()->toArray()
|
||||
);
|
||||
});
|
||||
|
||||
test('create', function () {
|
||||
Event::fake([
|
||||
Created::class,
|
||||
]);
|
||||
|
||||
$data = ['name' => 'foo', 'display_name' => 'Foo'];
|
||||
$role = $this->repo->create($data);
|
||||
|
||||
$this->assertDatabaseHas('roles', $data + ['id' => $role->id]);
|
||||
|
||||
Event::assertDispatched(Created::class);
|
||||
});
|
||||
|
||||
test('update', function () {
|
||||
Event::fake([
|
||||
Updated::class,
|
||||
]);
|
||||
|
||||
$role = Role::factory()->create();
|
||||
|
||||
$data = ['name' => 'foo', 'display_name' => 'Foo'];
|
||||
|
||||
$this->repo->update($role->id, $data);
|
||||
|
||||
$this->assertDatabaseHas('roles', $data + ['id' => $role->id]);
|
||||
|
||||
Event::assertDispatched(Updated::class);
|
||||
});
|
||||
|
||||
test('delete role', function () {
|
||||
Event::fake([
|
||||
Deleted::class,
|
||||
]);
|
||||
|
||||
$role = Role::factory()->create();
|
||||
|
||||
$this->repo->delete($role->id);
|
||||
|
||||
$this->assertDatabaseMissing('roles', ['id' => $role->id]);
|
||||
|
||||
Event::assertDispatched(Deleted::class);
|
||||
});
|
||||
|
||||
test('update permissions', function () {
|
||||
$role = Role::factory()->create();
|
||||
$permissions = Permission::factory()->times(2)->create();
|
||||
|
||||
$this->repo->updatePermissions($role->id, $permissions->pluck('id')->toArray());
|
||||
|
||||
$this->assertDatabaseHas('permission_role', ['role_id' => $role->id, 'permission_id' => $permissions[0]->id]);
|
||||
$this->assertDatabaseHas('permission_role', ['role_id' => $role->id, 'permission_id' => $permissions[1]->id]);
|
||||
});
|
||||
|
||||
test('lists', function () {
|
||||
$roles = Role::factory()->times(4)->create();
|
||||
$roles = $roles->pluck('display_name', 'id');
|
||||
|
||||
$this->assertEquals($roles->toArray(), $this->repo->lists()->toArray());
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use App\Repositories\Session\DbSession;
|
||||
use Carbon\Carbon;
|
||||
use Faker\Generator;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->repo = app(DbSession::class);
|
||||
});
|
||||
|
||||
test('get user session', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$data1 = getSessionStubData($user);
|
||||
$data2 = getSessionStubData($user);
|
||||
|
||||
DB::table('sessions')->insert($data1);
|
||||
DB::table('sessions')->insert($data2);
|
||||
|
||||
$expected = collect([
|
||||
(object) addAddMissingFields($data1),
|
||||
(object) addAddMissingFields($data2),
|
||||
]);
|
||||
$expected = $expected->sortBy('id')->keyBy('id')->toArray();
|
||||
|
||||
$actual = $this->repo->getUserSessions($user->id)
|
||||
->sortBy('id')
|
||||
->keyBy('id')
|
||||
->toArray();
|
||||
|
||||
$this->assertEquals($expected, $actual);
|
||||
});
|
||||
|
||||
function addAddMissingFields(array $data)
|
||||
{
|
||||
$agent = app('agent');
|
||||
$agent->setUserAgent($data['user_agent']);
|
||||
|
||||
return array_merge($data, [
|
||||
'browser' => $agent->browser(),
|
||||
'platform' => $agent->platform(),
|
||||
'device' => $agent->device(),
|
||||
'last_activity' => Carbon::createFromTimestamp($data['last_activity']),
|
||||
]);
|
||||
}
|
||||
|
||||
test('if get user sessions will return active sessions only', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$data1 = getSessionStubData($user);
|
||||
$data2 = getSessionStubData($user);
|
||||
$data2['last_activity'] = Carbon::now()->subMinutes(config('session.lifetime') + 1)->timestamp;
|
||||
|
||||
DB::table('sessions')->insert($data1);
|
||||
DB::table('sessions')->insert($data2);
|
||||
|
||||
$expected = collect([
|
||||
(object) addAddMissingFields($data1),
|
||||
]);
|
||||
|
||||
$expected = $expected->sortBy('id')->keyBy('id')->toArray();
|
||||
|
||||
$actual = $this->repo->getUserSessions($user->id)
|
||||
->sortBy('id')
|
||||
->keyBy('id')
|
||||
->toArray();
|
||||
|
||||
$this->assertEquals($expected, $actual);
|
||||
});
|
||||
|
||||
test('invalidate user session', function () {
|
||||
$user = User::factory()->create([
|
||||
'remember_token' => Str::random(60),
|
||||
]);
|
||||
|
||||
$data = getSessionStubData($user);
|
||||
DB::table('sessions')->insert($data);
|
||||
|
||||
$this->repo->invalidateSession($data['id']);
|
||||
|
||||
$this->assertDatabaseMissing('sessions', $data)
|
||||
->assertDatabaseHas('users', ['remember_token' => null]);
|
||||
});
|
||||
|
||||
test('invalidate all sessions for user', function () {
|
||||
$user = User::factory()->create([
|
||||
'remember_token' => Str::random(60),
|
||||
]);
|
||||
|
||||
$data = getSessionStubData($user);
|
||||
DB::table('sessions')->insert($data);
|
||||
|
||||
$this->repo->invalidateAllSessionsForUser($user->id);
|
||||
|
||||
$this->assertDatabaseMissing('sessions', ['user_id' => $user->id])
|
||||
->assertDatabaseHas('users', ['remember_token' => null]);
|
||||
});
|
||||
|
||||
function getSessionStubData($user)
|
||||
{
|
||||
$faker = app(Generator::class);
|
||||
|
||||
return [
|
||||
'id' => Str::random(),
|
||||
'user_id' => $user->id,
|
||||
'ip_address' => $faker->ipv4,
|
||||
'user_agent' => $faker->userAgent,
|
||||
'payload' => 'foo',
|
||||
'last_activity' => Carbon::now()->timestamp,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Repositories\User\EloquentUser;
|
||||
use App\Support\Enum\UserStatus;
|
||||
use Carbon\Carbon;
|
||||
use Facades\Tests\Setup\UserFactory;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Testing\Assert;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->repo = app(EloquentUser::class);
|
||||
});
|
||||
|
||||
test('test find', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->assertTrue($user->is($this->repo->findByEmail($user->email)));
|
||||
|
||||
$this->assertNull($this->repo->find(123));
|
||||
});
|
||||
|
||||
test('test find by email', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->assertTrue($user->is($this->repo->findByEmail($user->email)));
|
||||
|
||||
$this->assertNull($this->repo->findByEmail('foo@bar.com'));
|
||||
});
|
||||
|
||||
test('test find by social id', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
DB::table('social_logins')->insert([
|
||||
'user_id' => $user->id,
|
||||
'provider' => 'foo',
|
||||
'provider_id' => '123',
|
||||
'avatar' => '',
|
||||
'created_at' => Carbon::now(),
|
||||
]);
|
||||
|
||||
Assert::assertArraySubset(
|
||||
$user->toArray(),
|
||||
$this->repo->findBySocialId('foo', '123')->toArray()
|
||||
);
|
||||
|
||||
$this->assertNull($this->repo->findBySocialId('bar', '111'));
|
||||
});
|
||||
|
||||
test('test find by session id', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$sessionId = Str::random(40);
|
||||
|
||||
DB::table('sessions')->insert([
|
||||
'id' => $sessionId,
|
||||
'user_id' => $user->id,
|
||||
'ip_address' => '127.0.0.1',
|
||||
'user_agent' => 'foo',
|
||||
'payload' => Str::random(),
|
||||
'last_activity' => Carbon::now(),
|
||||
]);
|
||||
|
||||
Assert::assertArraySubset(
|
||||
$user->toArray(),
|
||||
$this->repo->findBySessionId($sessionId)->toArray()
|
||||
);
|
||||
});
|
||||
|
||||
test('test create', function () {
|
||||
$data = User::factory()->make()->toArray();
|
||||
|
||||
$this->repo->create($data + ['password' => 'foo']);
|
||||
|
||||
$this->assertDatabaseHas('users', $data);
|
||||
});
|
||||
|
||||
test('test associate facebook account to a user', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$socialUser = new Laravel\Socialite\One\User;
|
||||
$socialUser->map([
|
||||
'id' => '123',
|
||||
'avatar' => 'foo',
|
||||
'avatar_original' => 'foo?width=1920',
|
||||
]);
|
||||
|
||||
$this->repo->associateSocialAccountForUser($user->id, 'facebook', $socialUser);
|
||||
|
||||
$this->assertDatabaseHas('social_logins', [
|
||||
'user_id' => $user->id,
|
||||
'provider' => 'facebook',
|
||||
'provider_id' => '123',
|
||||
'avatar' => 'foo?width=150',
|
||||
'created_at' => Carbon::now(),
|
||||
]);
|
||||
|
||||
Carbon::setTestNow(null);
|
||||
});
|
||||
|
||||
test('test associate twitter account to a user', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$socialUser = new Laravel\Socialite\One\User;
|
||||
$socialUser->map([
|
||||
'id' => '123',
|
||||
'avatar' => 'foo_normal.jpg',
|
||||
]);
|
||||
|
||||
$this->repo->associateSocialAccountForUser($user->id, 'twitter', $socialUser);
|
||||
|
||||
$this->assertDatabaseHas('social_logins', [
|
||||
'user_id' => $user->id,
|
||||
'provider' => 'twitter',
|
||||
'provider_id' => '123',
|
||||
'avatar' => 'foo_200x200.jpg',
|
||||
'created_at' => Carbon::now(),
|
||||
]);
|
||||
|
||||
Carbon::setTestNow(null);
|
||||
});
|
||||
|
||||
test('test associate google account to a user', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$socialUser = new Laravel\Socialite\One\User;
|
||||
$socialUser->map([
|
||||
'id' => '123',
|
||||
'avatar' => 'avatar.jpg',
|
||||
'avatar_original' => 'avatar_original.jpg',
|
||||
]);
|
||||
|
||||
$this->repo->associateSocialAccountForUser($user->id, 'google', $socialUser);
|
||||
|
||||
$this->assertDatabaseHas('social_logins', [
|
||||
'user_id' => $user->id,
|
||||
'provider' => 'google',
|
||||
'provider_id' => '123',
|
||||
'avatar' => 'avatar_original.jpg?sz=150',
|
||||
'created_at' => Carbon::now(),
|
||||
]);
|
||||
|
||||
Carbon::setTestNow(null);
|
||||
});
|
||||
|
||||
test('test paginate', function () {
|
||||
$users = User::factory()->times(5)->create();
|
||||
$users = $users->sortByDesc('id')->values();
|
||||
|
||||
$result = $this->repo->paginate(2)->toArray();
|
||||
|
||||
$this->assertEquals(2, count($result['data']));
|
||||
$this->assertEquals(5, $result['total']);
|
||||
Assert::assertArraySubset($users[0]->toArray(), $result['data'][0]);
|
||||
Assert::assertArraySubset($users[1]->toArray(), $result['data'][1]);
|
||||
});
|
||||
|
||||
test('test paginate with status', function () {
|
||||
User::factory()->times(3)->create();
|
||||
User::factory()->create(['status' => UserStatus::BANNED]);
|
||||
|
||||
$active = $this->repo->paginate(2, null, UserStatus::ACTIVE->value)->toArray();
|
||||
$banned = $this->repo->paginate(2, null, UserStatus::BANNED->value)->toArray();
|
||||
|
||||
$this->assertEquals(2, count($active['data']));
|
||||
$this->assertEquals(3, $active['total']);
|
||||
|
||||
$this->assertEquals(1, count($banned['data']));
|
||||
$this->assertEquals(1, $banned['total']);
|
||||
});
|
||||
|
||||
test('test paginate with search', function () {
|
||||
User::factory()->create([
|
||||
'first_name' => 'John',
|
||||
'last_name' => 'Doe',
|
||||
'username' => 'jdoe',
|
||||
'email' => 'joe@test.com',
|
||||
]);
|
||||
|
||||
User::factory()->create([
|
||||
'first_name' => 'Jane',
|
||||
'last_name' => 'Doe',
|
||||
'username' => 'janedoe',
|
||||
'email' => 'jane@doe.com',
|
||||
]);
|
||||
|
||||
User::factory()->create([
|
||||
'first_name' => 'Milos',
|
||||
'last_name' => 'Stojanovic',
|
||||
'email' => 'test@test.com',
|
||||
]);
|
||||
|
||||
$this->assertEquals(2, $this->repo->paginate(25, 'doe')->total());
|
||||
$this->assertEquals(1, $this->repo->paginate(25, 'Milos')->total());
|
||||
$this->assertEquals(2, $this->repo->paginate(25, 'test')->total());
|
||||
$this->assertEquals(2, $this->repo->paginate(25, 'an')->total());
|
||||
});
|
||||
|
||||
test('test update user', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$data = [
|
||||
'first_name' => 'John',
|
||||
'last_name' => 'Doe',
|
||||
'username' => 'foo',
|
||||
'email' => 'test@test.com',
|
||||
];
|
||||
|
||||
$this->repo->update($user->id, $data);
|
||||
|
||||
$this->assertDatabaseHas('users', $data + ['id' => $user->id]);
|
||||
});
|
||||
|
||||
test('test update when provided country is zero', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$data = [
|
||||
'first_name' => 'John',
|
||||
'last_name' => 'Doe',
|
||||
'username' => 'foo',
|
||||
'email' => 'test@test.com',
|
||||
'country_id' => 0,
|
||||
];
|
||||
|
||||
$this->repo->update($user->id, $data);
|
||||
|
||||
$this->assertDatabaseHas('users', array_merge($data, ['id' => $user->id, 'country_id' => null]));
|
||||
});
|
||||
|
||||
test('test delete user', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->repo->delete($user->id);
|
||||
|
||||
$this->assertDatabaseMissing('users', ['id' => $user->id]);
|
||||
});
|
||||
|
||||
test('test count users', function () {
|
||||
User::factory()->times(7)->create();
|
||||
|
||||
$this->assertEquals(7, $this->repo->count());
|
||||
});
|
||||
|
||||
test('test new users count', function () {
|
||||
Carbon::setTestNow(Carbon::now()->startOfMonth()->subMonth());
|
||||
User::factory()->times(3)->create();
|
||||
|
||||
Carbon::setTestNow(null);
|
||||
User::factory()->times(5)->create();
|
||||
|
||||
$this->assertEquals(5, $this->repo->newUsersCount());
|
||||
});
|
||||
|
||||
test('test count by status', function () {
|
||||
User::factory()->times(3)->create();
|
||||
User::factory()->create(['status' => UserStatus::BANNED]);
|
||||
User::factory()->times(2)->create(['status' => UserStatus::UNCONFIRMED]);
|
||||
|
||||
$this->assertEquals(3, $this->repo->countByStatus(UserStatus::ACTIVE));
|
||||
$this->assertEquals(1, $this->repo->countByStatus(UserStatus::BANNED));
|
||||
$this->assertEquals(2, $this->repo->countByStatus(UserStatus::UNCONFIRMED));
|
||||
});
|
||||
|
||||
test('test latest', function () {
|
||||
Carbon::setTestNow(now()->subDay());
|
||||
$user4 = User::factory()->create();
|
||||
|
||||
Carbon::setTestNow(now()->subMinutes(3));
|
||||
$user3 = User::factory()->create();
|
||||
|
||||
Carbon::setTestNow(now()->subMinutes(2));
|
||||
$user2 = User::factory()->create();
|
||||
|
||||
Carbon::setTestNow(now()->subMinutes(1));
|
||||
$user1 = User::factory()->create();
|
||||
|
||||
$latestTwo = $this->repo->latest(2);
|
||||
$latestFour = $this->repo->latest(4);
|
||||
|
||||
$this->assertEquals(2, count($latestTwo));
|
||||
$this->assertEquals(4, count($latestFour));
|
||||
|
||||
Assert::assertArraySubset($user4->toArray(), $latestTwo[0]->toArray());
|
||||
Assert::assertArraySubset($user3->toArray(), $latestTwo[1]->toArray());
|
||||
Assert::assertArraySubset($user1->toArray(), $latestFour[3]->toArray());
|
||||
});
|
||||
|
||||
test('test count of new users per month', function () {
|
||||
$from = now()->startOfYear();
|
||||
|
||||
Carbon::setTestNow($from);
|
||||
User::factory()->times(2)->create();
|
||||
|
||||
Carbon::setTestNow($from->copy()->addMonths(2));
|
||||
User::factory()->times(4)->create();
|
||||
|
||||
Carbon::setTestNow($from->copy()->addMonths(6));
|
||||
User::factory()->times(2)->create();
|
||||
|
||||
Carbon::setTestNow($from->copy()->addMonths(7));
|
||||
User::factory()->times(1)->create();
|
||||
|
||||
Carbon::setTestNow($from->copy()->addMonths(10));
|
||||
User::factory()->times(4)->create();
|
||||
|
||||
Carbon::setTestNow(null);
|
||||
|
||||
$currentYear = now()->year;
|
||||
|
||||
$expected = [
|
||||
"January {$currentYear}" => 2,
|
||||
"February {$currentYear}" => 0,
|
||||
"March {$currentYear}" => 4,
|
||||
"April {$currentYear}" => 0,
|
||||
"May {$currentYear}" => 0,
|
||||
"June {$currentYear}" => 0,
|
||||
"July {$currentYear}" => 2,
|
||||
"August {$currentYear}" => 1,
|
||||
"September {$currentYear}" => 0,
|
||||
"October {$currentYear}" => 0,
|
||||
"November {$currentYear}" => 4,
|
||||
"December {$currentYear}" => 0,
|
||||
];
|
||||
|
||||
$usersPerMonth = $this->repo->countOfNewUsersPerMonthPerRole(
|
||||
now()->startOfYear(),
|
||||
now()->endOfYear()
|
||||
);
|
||||
|
||||
$this->assertEquals($expected, $usersPerMonth);
|
||||
});
|
||||
|
||||
test('test get users with specific role', function () {
|
||||
$this->artisan('db:seed --class=RolesSeeder');
|
||||
|
||||
$admin = UserFactory::admin()->create();
|
||||
$user = UserFactory::user()->create();
|
||||
|
||||
$result = $this->repo->getUsersWithRole('Admin');
|
||||
|
||||
$this->assertEquals(1, $result->count());
|
||||
$this->assertTrue($admin->is($result[0]));
|
||||
|
||||
$result = $this->repo->getUsersWithRole('User');
|
||||
$this->assertEquals(1, $result->count());
|
||||
$this->assertTrue($user->is($result[0]));
|
||||
});
|
||||
|
||||
test('test set role', function () {
|
||||
$this->artisan('db:seed --class=RolesSeeder');
|
||||
|
||||
$user = UserFactory::user()->create();
|
||||
$role = Role::where('name', 'Admin')->first();
|
||||
|
||||
$this->repo->setRole($user->id, $role->id);
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'role_id' => $role->id,
|
||||
'id' => $user->id,
|
||||
]);
|
||||
});
|
||||
|
||||
test('test switch roles for a user', function () {
|
||||
$this->artisan('db:seed --class=RolesSeeder');
|
||||
|
||||
$adminRole = Role::where('name', 'Admin')->first();
|
||||
|
||||
$userA = UserFactory::user()->create();
|
||||
$userB = UserFactory::user()->create();
|
||||
|
||||
$this->repo->switchRolesForUsers($userA->role_id, $adminRole->id);
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'role_id' => $adminRole->id,
|
||||
'id' => $userA->id,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'role_id' => $adminRole->id,
|
||||
'id' => $userB->id,
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
use App\Rules\ValidPermissionName;
|
||||
|
||||
test('passes for valid names', function (string $name) {
|
||||
$rule = new ValidPermissionName;
|
||||
|
||||
$this->assertTrue($rule->passes('name', $name));
|
||||
})->with('validNames');
|
||||
|
||||
test('fails for invalid names', function (mixed $name) {
|
||||
$rule = new ValidPermissionName;
|
||||
|
||||
$this->assertFalse($rule->passes('name', $name));
|
||||
})->with('invalidNames');
|
||||
|
||||
test('validate calls fail with translated message when invalid', function () {
|
||||
$rule = new ValidPermissionName;
|
||||
$messages = [];
|
||||
|
||||
$rule->validate('name', 'has space', function ($msg) use (&$messages) {
|
||||
$messages[] = $msg;
|
||||
});
|
||||
|
||||
$this->assertNotEmpty($messages);
|
||||
});
|
||||
|
||||
test('validate does not call fail when valid', function () {
|
||||
$rule = new ValidPermissionName;
|
||||
$called = false;
|
||||
|
||||
$rule->validate('name', 'users.manage', function () use (&$called) {
|
||||
$called = true;
|
||||
});
|
||||
|
||||
$this->assertFalse($called);
|
||||
});
|
||||
|
||||
test('to string returns regex form', function () {
|
||||
$rule = new ValidPermissionName;
|
||||
|
||||
$this->assertStringStartsWith('regex:', (string) $rule);
|
||||
});
|
||||
|
||||
dataset('validNames', function () {
|
||||
return [
|
||||
'alphanumeric' => ['users123'],
|
||||
'dot-separated' => ['users.manage'],
|
||||
'underscore' => ['users_manage'],
|
||||
'hyphen' => ['users-manage'],
|
||||
'numeric string' => ['12345'],
|
||||
];
|
||||
});
|
||||
|
||||
dataset('invalidNames', function () {
|
||||
return [
|
||||
'space' => ['has space'],
|
||||
'special char' => ['users@manage'],
|
||||
'slash' => ['users/manage'],
|
||||
'array' => [['users.manage']],
|
||||
];
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Repositories\Role\RoleRepository;
|
||||
use App\Services\Auth\Social\SocialManager;
|
||||
use App\Support\Enum\UserStatus;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Socialite\Contracts\User as SocialUser;
|
||||
use Tests\UpdatesSettings;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
uses(UpdatesSettings::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('associates existing user when email matches', function () {
|
||||
$existing = User::factory()->create(['email' => 'foo@bar.com']);
|
||||
|
||||
$socialUser = makeSocialUser('foo@bar.com', 'John Doe');
|
||||
|
||||
$manager = app(SocialManager::class);
|
||||
$user = $manager->associate($socialUser, 'facebook');
|
||||
|
||||
$this->assertTrue($existing->is($user));
|
||||
$this->assertDatabaseHas('social_logins', [
|
||||
'user_id' => $user->id,
|
||||
'provider' => 'facebook',
|
||||
]);
|
||||
});
|
||||
|
||||
test('creates new user when email does not exist', function () {
|
||||
$this->setSettings(['approval.enabled' => false]);
|
||||
|
||||
$socialUser = makeSocialUser('new@bar.com', 'Jane Smith');
|
||||
|
||||
$manager = app(SocialManager::class);
|
||||
$user = $manager->associate($socialUser, 'google');
|
||||
|
||||
$this->assertSame('new@bar.com', $user->email);
|
||||
$this->assertSame('Jane', $user->first_name);
|
||||
$this->assertSame('Smith', $user->last_name);
|
||||
$this->assertSame(UserStatus::ACTIVE, $user->status);
|
||||
$this->assertNotNull($user->email_verified_at);
|
||||
$this->assertDatabaseHas('social_logins', [
|
||||
'user_id' => $user->id,
|
||||
'provider' => 'google',
|
||||
]);
|
||||
});
|
||||
|
||||
test('new user status is waiting approval when approval enabled', function () {
|
||||
$this->setSettings(['approval.enabled' => true]);
|
||||
|
||||
$socialUser = makeSocialUser('newer@bar.com', 'Foo Bar');
|
||||
|
||||
$manager = app(SocialManager::class);
|
||||
$user = $manager->associate($socialUser, 'google');
|
||||
|
||||
$this->assertSame(UserStatus::WAITING_APPROVAL, $user->status);
|
||||
});
|
||||
|
||||
test('single name is parsed without last name', function () {
|
||||
$socialUser = makeSocialUser('single@bar.com', 'Cher');
|
||||
|
||||
$manager = app(SocialManager::class);
|
||||
$user = $manager->associate($socialUser, 'facebook');
|
||||
|
||||
$this->assertSame('Cher', $user->first_name);
|
||||
$this->assertSame('', $user->last_name);
|
||||
});
|
||||
|
||||
test('new user gets default user role', function () {
|
||||
$socialUser = makeSocialUser('role@bar.com', 'Role User');
|
||||
|
||||
$manager = app(SocialManager::class);
|
||||
$user = $manager->associate($socialUser, 'facebook');
|
||||
|
||||
$this->assertSame(
|
||||
app(RoleRepository::class)->findByName(Role::DEFAULT_USER_ROLE)->id,
|
||||
$user->role_id,
|
||||
);
|
||||
});
|
||||
|
||||
test('facebook avatar url is resized', function () {
|
||||
$socialUser = makeSocialUser('fb@bar.com', 'FB User');
|
||||
$socialUser->avatar_original = 'https://avatar.fb/me?width=1920';
|
||||
|
||||
$manager = app(SocialManager::class);
|
||||
$user = $manager->associate($socialUser, 'facebook');
|
||||
|
||||
$this->assertSame('https://avatar.fb/me?width=150', $user->avatar);
|
||||
});
|
||||
|
||||
function makeSocialUser(string $email, string $name): SocialUser
|
||||
{
|
||||
$socialUser = Mockery::mock(SocialUser::class);
|
||||
$socialUser->shouldReceive('getId')->andReturn((string) fake()->randomNumber());
|
||||
$socialUser->shouldReceive('getEmail')->andReturn($email);
|
||||
$socialUser->shouldReceive('getName')->andReturn($name);
|
||||
$socialUser->shouldReceive('getAvatar')->andReturn('https://example.com/avatar.jpg');
|
||||
$socialUser->avatar_original = 'https://example.com/avatar.jpg';
|
||||
|
||||
return $socialUser;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
use App\Services\Auth\TwoFactor\Facade;
|
||||
|
||||
test('facade accessor is authy', function () {
|
||||
$method = new ReflectionMethod(Facade::class, 'getFacadeAccessor');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$this->assertSame('authy', $method->invoke(null));
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Permission;
|
||||
use App\Models\Role;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('attach and detach permission', function () {
|
||||
$role = Role::factory()->create();
|
||||
$permission = Permission::factory()->create();
|
||||
|
||||
$role->attachPermission($permission);
|
||||
$this->assertTrue($role->fresh()->hasPermission($permission->name));
|
||||
|
||||
$role->detachPermission($permission);
|
||||
$this->assertFalse($role->fresh()->hasPermission($permission->name));
|
||||
});
|
||||
|
||||
test('attach permissions supports id and object array', function () {
|
||||
$role = Role::factory()->create();
|
||||
$p1 = Permission::factory()->create();
|
||||
$p2 = Permission::factory()->create();
|
||||
|
||||
$role->attachPermissions([$p1, ['id' => $p2->id]]);
|
||||
|
||||
$this->assertTrue($role->fresh()->hasPermission($p1->name));
|
||||
$this->assertTrue($role->fresh()->hasPermission($p2->name));
|
||||
});
|
||||
|
||||
test('sync permissions replaces permissions', function () {
|
||||
$role = Role::factory()->create();
|
||||
$p1 = Permission::factory()->create();
|
||||
$p2 = Permission::factory()->create();
|
||||
|
||||
$role->attachPermission($p1);
|
||||
$role->syncPermissions([$p2->id]);
|
||||
|
||||
$fresh = $role->fresh();
|
||||
$this->assertFalse($fresh->hasPermission($p1->name));
|
||||
$this->assertTrue($fresh->hasPermission($p2->name));
|
||||
});
|
||||
|
||||
test('save permissions with empty array detaches all', function () {
|
||||
$role = Role::factory()->create();
|
||||
$p1 = Permission::factory()->create();
|
||||
|
||||
$role->attachPermission($p1);
|
||||
$role->savePermissions([]);
|
||||
|
||||
$this->assertCount(0, $role->fresh()->permissions);
|
||||
});
|
||||
|
||||
test('cached permissions are returned', function () {
|
||||
$role = Role::factory()->create();
|
||||
$p1 = Permission::factory()->create();
|
||||
$role->attachPermission($p1);
|
||||
|
||||
$cached = $role->cachedPermissions();
|
||||
|
||||
$this->assertTrue($cached->contains('name', $p1->name));
|
||||
});
|
||||
|
||||
test('save clears cache', function () {
|
||||
$role = Role::factory()->create();
|
||||
$p1 = Permission::factory()->create();
|
||||
$role->attachPermission($p1);
|
||||
|
||||
$role->cachedPermissions();
|
||||
$role->detachPermission($p1);
|
||||
$role->save();
|
||||
|
||||
$this->assertFalse($role->cachedPermissions()->contains('name', $p1->name));
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Permission;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('has role returns true when matching role', function () {
|
||||
$role = Role::factory()->create(['name' => 'Editor']);
|
||||
$user = User::factory()->create(['role_id' => $role->id]);
|
||||
|
||||
$this->assertTrue($user->hasRole('Editor'));
|
||||
$this->assertFalse($user->hasRole('Admin'));
|
||||
});
|
||||
|
||||
test('has permission returns true when role has permission', function () {
|
||||
$role = Role::factory()->create();
|
||||
$permission = Permission::factory()->create(['name' => 'something.do']);
|
||||
$role->attachPermission($permission);
|
||||
|
||||
$user = User::factory()->create(['role_id' => $role->id]);
|
||||
|
||||
$this->assertTrue($user->hasPermission('something.do'));
|
||||
$this->assertFalse($user->hasPermission('something.else'));
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use App\Support\Authorization\PasswordChangeManager;
|
||||
use Illuminate\Cache\CacheManager;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\UpdatesSettings;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
uses(UpdatesSettings::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
|
||||
$this->manager = new PasswordChangeManager(app(CacheManager::class));
|
||||
});
|
||||
|
||||
test('force password change returns false when feature disabled', function () {
|
||||
$this->setSettings(['password-change.enabled' => false]);
|
||||
|
||||
$user = User::factory()->create(['force_password_change' => true]);
|
||||
|
||||
$this->assertFalse($this->manager->forcePasswordChange($user));
|
||||
$this->assertFalse($this->manager->isPasswordChangeForcedForUser($user));
|
||||
});
|
||||
|
||||
test('force password change returns false when user flag false', function () {
|
||||
$this->setSettings(['password-change.enabled' => true]);
|
||||
|
||||
$user = User::factory()->create(['force_password_change' => false]);
|
||||
|
||||
$this->assertFalse($this->manager->forcePasswordChange($user));
|
||||
});
|
||||
|
||||
test('force password change sets cache when enabled and flagged', function () {
|
||||
$this->setSettings(['password-change.enabled' => true]);
|
||||
|
||||
$user = User::factory()->create(['force_password_change' => true]);
|
||||
|
||||
$this->assertTrue($this->manager->forcePasswordChange($user));
|
||||
$this->assertTrue($this->manager->isPasswordChangeForcedForUser($user));
|
||||
});
|
||||
|
||||
test('lift password change request clears cache', function () {
|
||||
$this->setSettings(['password-change.enabled' => true]);
|
||||
|
||||
$user = User::factory()->create(['force_password_change' => true]);
|
||||
|
||||
$this->manager->forcePasswordChange($user);
|
||||
$this->manager->liftPasswordChangeRequest($user);
|
||||
|
||||
$this->assertFalse($this->manager->isPasswordChangeForcedForUser($user));
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use App\Support\Demo;
|
||||
|
||||
beforeEach(function () {
|
||||
config([
|
||||
'demo.enabled' => true,
|
||||
'demo.protected_users' => ['admin'],
|
||||
'demo.message' => null,
|
||||
]);
|
||||
});
|
||||
|
||||
test('nobody is protected while demo mode is off', function () {
|
||||
config(['demo.enabled' => false]);
|
||||
|
||||
$this->assertFalse(Demo::enabled());
|
||||
$this->assertFalse(Demo::isProtectedUser(new User(['username' => 'admin'])));
|
||||
});
|
||||
|
||||
test('a guest is never protected', function () {
|
||||
$this->assertFalse(Demo::isProtectedUser(null));
|
||||
});
|
||||
|
||||
test('a protected account is matched on its username', function () {
|
||||
$this->assertTrue(Demo::isProtectedUser(new User(['username' => 'admin'])));
|
||||
});
|
||||
|
||||
test('a protected account is matched on its email', function () {
|
||||
config(['demo.protected_users' => ['admin@example.com']]);
|
||||
|
||||
$this->assertTrue(Demo::isProtectedUser(new User(['email' => 'admin@example.com'])));
|
||||
});
|
||||
|
||||
test('matching ignores case on both sides', function () {
|
||||
config(['demo.protected_users' => ['ADMIN']]);
|
||||
|
||||
$this->assertTrue(Demo::isProtectedUser(new User(['username' => 'Admin'])));
|
||||
});
|
||||
|
||||
test('an ordinary account is not protected', function () {
|
||||
$this->assertFalse(Demo::isProtectedUser(new User([
|
||||
'username' => 'jane',
|
||||
'email' => 'jane@example.com',
|
||||
])));
|
||||
});
|
||||
|
||||
test('a user without a username is not protected by an empty match', function () {
|
||||
config(['demo.protected_users' => ['']]);
|
||||
|
||||
$this->assertFalse(Demo::isProtectedUser(new User(['email' => 'jane@example.com'])));
|
||||
});
|
||||
|
||||
test('the configured message overrides every reason', function () {
|
||||
config(['demo.message' => 'Nope.']);
|
||||
|
||||
$this->assertSame('Nope.', Demo::message());
|
||||
$this->assertSame('Nope.', Demo::message('Something more specific.'));
|
||||
});
|
||||
|
||||
test('a reason falls back to the generic refusal', function () {
|
||||
$this->assertSame('This action is disabled in the demo.', Demo::message());
|
||||
$this->assertSame('Something more specific.', Demo::message('Something more specific.'));
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use App\Support\Enum\UserStatus;
|
||||
use Tests\UpdatesSettings;
|
||||
|
||||
uses(UpdatesSettings::class);
|
||||
|
||||
test('lists returns active banned unconfirmed when approval disabled', function () {
|
||||
$this->setSettings(['approval.enabled' => false]);
|
||||
|
||||
$statuses = UserStatus::lists();
|
||||
|
||||
$this->assertArrayHasKey(UserStatus::ACTIVE->value, $statuses);
|
||||
$this->assertArrayHasKey(UserStatus::BANNED->value, $statuses);
|
||||
$this->assertArrayHasKey(UserStatus::UNCONFIRMED->value, $statuses);
|
||||
$this->assertArrayNotHasKey(UserStatus::WAITING_APPROVAL->value, $statuses);
|
||||
});
|
||||
|
||||
test('lists includes waiting approval when approval enabled', function () {
|
||||
$this->setSettings(['approval.enabled' => true]);
|
||||
|
||||
$statuses = UserStatus::lists();
|
||||
|
||||
$this->assertArrayHasKey(UserStatus::WAITING_APPROVAL->value, $statuses);
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use App\Support\Locale;
|
||||
|
||||
test('flag url returns url for known locale', function () {
|
||||
$this->assertStringContainsString('/flags/GB.png', Locale::flagUrl('en'));
|
||||
$this->assertStringContainsString('/flags/DE.png', Locale::flagUrl('de'));
|
||||
$this->assertStringContainsString('/flags/RS.png', Locale::flagUrl('sr'));
|
||||
});
|
||||
|
||||
test('flag url returns null for unknown locale', function () {
|
||||
$this->assertNull(Locale::flagUrl('fr'));
|
||||
});
|
||||
|
||||
test('validate locale accepts available locales', function () {
|
||||
foreach (Locale::AVAILABLE_LOCALES as $locale) {
|
||||
$this->assertTrue(Locale::validateLocale($locale));
|
||||
}
|
||||
});
|
||||
|
||||
test('validate locale rejects unknown locale', function () {
|
||||
$this->assertFalse(Locale::validateLocale('fr'));
|
||||
$this->assertFalse(Locale::validateLocale(''));
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use App\Support\Sidebar\Item;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->artisan('db:seed');
|
||||
});
|
||||
|
||||
test('create builds item with title', function () {
|
||||
$item = Item::create('Users');
|
||||
|
||||
$this->assertSame('Users', $item->getTitle());
|
||||
});
|
||||
|
||||
test('href takes priority over route', function () {
|
||||
$item = Item::create('Foo')
|
||||
->href('https://example.com/bar');
|
||||
|
||||
$this->assertSame('https://example.com/bar', $item->getHref());
|
||||
});
|
||||
|
||||
test('get href resolves named route', function () {
|
||||
$item = Item::create('Foo')->route('dashboard');
|
||||
|
||||
$this->assertSame(route('dashboard'), $item->getHref());
|
||||
});
|
||||
|
||||
test('get href returns null when neither set', function () {
|
||||
$this->assertNull(Item::create('Foo')->getHref());
|
||||
});
|
||||
|
||||
test('icon setter and getter', function () {
|
||||
$item = Item::create('Foo')->icon('bi-people');
|
||||
|
||||
$this->assertSame('bi-people', $item->getIcon());
|
||||
});
|
||||
|
||||
test('active path setter and getter', function () {
|
||||
$item = Item::create('Foo')->active('users*');
|
||||
|
||||
$this->assertSame('users*', $item->getActivePath());
|
||||
});
|
||||
|
||||
test('dropdown returns false without children', function () {
|
||||
$this->assertFalse(Item::create('Foo')->isDropdown());
|
||||
});
|
||||
|
||||
test('add children makes item a dropdown', function () {
|
||||
$item = Item::create('Foo')->addChildren([
|
||||
Item::create('Child 1'),
|
||||
Item::create('Child 2'),
|
||||
]);
|
||||
|
||||
$this->assertTrue($item->isDropdown());
|
||||
$this->assertCount(2, $item->children());
|
||||
});
|
||||
|
||||
test('expanded path collects children active paths', function () {
|
||||
$item = Item::create('Foo')->addChildren([
|
||||
Item::create('Child 1')->active('users*'),
|
||||
Item::create('Child 2')->active('roles*'),
|
||||
]);
|
||||
|
||||
$this->assertSame(['users*', 'roles*'], $item->getExpandedPath());
|
||||
});
|
||||
|
||||
test('authorize returns true when no permissions set', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->assertTrue(Item::create('Foo')->authorize($user));
|
||||
});
|
||||
|
||||
test('authorize calls closure when closure provided', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$item = Item::create('Foo')->permissions(fn ($u) => $u->id === $user->id);
|
||||
|
||||
$this->assertTrue($item->authorize($user));
|
||||
});
|
||||
|
||||
test('authorize checks user has permission', function () {
|
||||
$user = Mockery::mock(User::class)->makePartial();
|
||||
$user->shouldReceive('hasPermission')->with('users.manage')->andReturn(true);
|
||||
$user->shouldReceive('hasPermission')->with('roles.manage')->andReturn(false);
|
||||
|
||||
$allowed = Item::create('A')->permissions(['users.manage']);
|
||||
$denied = Item::create('B')->permissions(['users.manage', 'roles.manage']);
|
||||
|
||||
$this->assertTrue($allowed->authorize($user));
|
||||
$this->assertFalse($denied->authorize($user));
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use App\View\Components\Logo;
|
||||
|
||||
test('default variant uses logo image', function () {
|
||||
$logo = new Logo;
|
||||
|
||||
$this->assertStringContainsString('assets/img/logo.png', $logo->src);
|
||||
});
|
||||
|
||||
test('no text variant uses no text image', function () {
|
||||
$logo = new Logo(variant: 'no-text');
|
||||
|
||||
$this->assertStringContainsString('assets/img/logo-no-text.png', $logo->src);
|
||||
});
|
||||
|
||||
test('height and class passthrough', function () {
|
||||
$logo = new Logo(class: 'header-logo', height: '32');
|
||||
|
||||
$this->assertSame('header-logo', $logo->class);
|
||||
$this->assertSame('32', $logo->height);
|
||||
});
|
||||
|
||||
test('render returns a view', function () {
|
||||
$rendered = (new Logo)->render();
|
||||
|
||||
$this->assertSame('components.logo', $rendered->name());
|
||||
});
|
||||
Reference in New Issue
Block a user