645 lines
19 KiB
PHP
645 lines
19 KiB
PHP
<?php
|
|
|
|
use App\Events\User\UpdatedByAdmin;
|
|
use App\Models\Role;
|
|
use App\Models\User;
|
|
use App\Support\Enum\UserStatus;
|
|
use Carbon\Carbon;
|
|
use Facades\Tests\Setup\RoleFactory;
|
|
use Facades\Tests\Setup\UserFactory;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Str;
|
|
use Inertia\Testing\AssertableInertia as Assert;
|
|
use Tests\UpdatesSettings;
|
|
|
|
uses(RefreshDatabase::class);
|
|
uses(UpdatesSettings::class);
|
|
|
|
beforeEach(function () {
|
|
$this->artisan('db:seed', ['--class' => 'RolesSeeder']);
|
|
$this->artisan('db:seed', ['--class' => 'PermissionsSeeder']);
|
|
$this->artisan('db:seed', ['--class' => 'CountriesSeeder']);
|
|
});
|
|
|
|
test('guests cannot view the user list page', function () {
|
|
$this->get('/users')->assertRedirect('/login');
|
|
});
|
|
|
|
test('users without appropriate permission cannot view user list page', function () {
|
|
$roleA = RoleFactory::create();
|
|
$roleB = RoleFactory::withPermissions('users.manage')->create();
|
|
|
|
$userA = UserFactory::role($roleA)->create();
|
|
$userB = UserFactory::role($roleB)->create();
|
|
|
|
$this->actingAs($userA)->get('/users')->assertForbidden();
|
|
$this->actingAs($userB)->get('/users')->assertOk();
|
|
});
|
|
|
|
test('user collection is being properly passed to the view', function () {
|
|
$admin = UserFactory::admin()->create();
|
|
$active = UserFactory::user()->create();
|
|
$banned = UserFactory::user()->banned()->create();
|
|
$unconfirmed = UserFactory::user()->unconfirmed()->create();
|
|
|
|
$ids = $this->actingAs($admin)->get('users')->viewData('page')['props']['users']['data'];
|
|
|
|
$ids = collect($ids)->pluck('id')->all();
|
|
|
|
$this->assertCount(4, $ids);
|
|
$this->assertContains($admin->id, $ids);
|
|
$this->assertContains($active->id, $ids);
|
|
$this->assertContains($banned->id, $ids);
|
|
$this->assertContains($unconfirmed->id, $ids);
|
|
});
|
|
|
|
test('user list is paginated', function () {
|
|
$this->beAdmin();
|
|
|
|
$this->get('users')->assertInertia(function (Assert $page) {
|
|
$page->component('users/Index')->has('users.data', 1);
|
|
|
|
$paginator = $page->toArray()['props']['users'];
|
|
|
|
$this->assertArrayHasKey('links', $paginator);
|
|
$this->assertSame(1, $paginator['current_page']);
|
|
});
|
|
});
|
|
|
|
test('users can be filtered out by search term', function () {
|
|
User::factory()->create(['first_name' => 'Milos', 'last_name' => 'Stojanovic']);
|
|
$user2 = User::factory()->create(['first_name' => 'John', 'last_name' => 'Doe']);
|
|
$user3 = User::factory()->create(['first_name' => 'Jane', 'last_name' => 'Doe']);
|
|
|
|
$ids = $this->actingAsAdmin()->get('users?search=doe')->viewData('page')['props']['users']['data'];
|
|
|
|
$ids = collect($ids)->pluck('id')->all();
|
|
|
|
$this->assertCount(2, $ids);
|
|
$this->assertContains($user3->id, $ids);
|
|
$this->assertContains($user2->id, $ids);
|
|
});
|
|
|
|
test('users can be filtered out by status', function () {
|
|
User::factory()->times(2)->create();
|
|
User::factory()->times(3)->create(['status' => UserStatus::UNCONFIRMED]);
|
|
|
|
$ids = $this->actingAsAdmin()
|
|
->get('users?status='.UserStatus::BANNED->value)
|
|
->viewData('page')['props']['users']['data'];
|
|
|
|
$ids = collect($ids)->pluck('id')->all();
|
|
|
|
$this->assertCount(0, $ids);
|
|
});
|
|
|
|
test('admin can successfully create new users', function () {
|
|
$data = validUserParams();
|
|
|
|
$this->actingAsAdmin()
|
|
->post('/users', $data)
|
|
->assertRedirect('/users');
|
|
|
|
$this->assertSessionHasSuccess('User created successfully.');
|
|
|
|
$user = User::where('email', $data['email'])->first();
|
|
|
|
$this->assertDatabaseHas('users', Arr::except($data, ['password', 'password_confirmation']));
|
|
$this->assertTrue(Hash::check('123123123', $user->password));
|
|
});
|
|
|
|
test('users without appropriate permissions cannot create new users', function () {
|
|
$roleA = RoleFactory::create();
|
|
$roleB = RoleFactory::withPermissions('users.manage')->create();
|
|
|
|
$userA = UserFactory::role($roleA)->create();
|
|
$userB = UserFactory::role($roleB)->create();
|
|
|
|
$data = validUserParams();
|
|
|
|
$this->actingAs($userA)->post('/users', $data)->assertForbidden();
|
|
$this->assertNUll(User::where('email', $data['email'])->first());
|
|
|
|
$this->actingAs($userB)->post('/users', $data)->assertRedirect('/users');
|
|
$this->assertNotNull(User::where('email', $data['email'])->first());
|
|
});
|
|
|
|
test('email field is required while creating a user', function () {
|
|
$data = Arr::except(validUserParams(), ['email']);
|
|
|
|
$this->actingAsAdmin()
|
|
->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users')
|
|
->assertSessionHasErrors('email');
|
|
});
|
|
|
|
test('email field must be a valid email while creating a user', function () {
|
|
$data = validUserParams(['email' => 'foo']);
|
|
|
|
$this->actingAsAdmin()
|
|
->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users')
|
|
->assertSessionHasErrors('email');
|
|
});
|
|
|
|
test('email must be unique while creating a user', function () {
|
|
$this->beAdmin();
|
|
|
|
UserFactory::user()->email('john@example.com')->create();
|
|
|
|
$data = validUserParams(['email' => 'john@example.com']);
|
|
|
|
$this->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users')
|
|
->assertSessionHasErrors('email');
|
|
});
|
|
|
|
test('username is not required', function () {
|
|
$this->beAdmin();
|
|
|
|
$data = validUserParams();
|
|
unset($data['username']);
|
|
|
|
$this->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users');
|
|
|
|
$this->assertNotNull(User::where('email', $data['email'])->first());
|
|
});
|
|
|
|
test('username must be unique while creating a user', function () {
|
|
$this->beAdmin();
|
|
|
|
User::factory()->create(['username' => 'johndoe']);
|
|
|
|
$data = validUserParams(['username' => 'johndoe']);
|
|
|
|
$this->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users')
|
|
->assertSessionHasErrors('username');
|
|
});
|
|
|
|
test('password is required while creating a user', function () {
|
|
$this->beAdmin();
|
|
|
|
$data = validUserParams();
|
|
unset($data['password']);
|
|
|
|
$this->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users')
|
|
->assertSessionHasErrors('password');
|
|
});
|
|
|
|
test('password must be at least 6 characters long while creating a user', function () {
|
|
$this->beAdmin();
|
|
|
|
$data = validUserParams(['password' => '12345']);
|
|
|
|
$this->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users')
|
|
->assertSessionHasErrors('password');
|
|
});
|
|
|
|
test('password must be confirmed while creating a user', function () {
|
|
$this->beAdmin();
|
|
|
|
$data = validUserParams();
|
|
unset($data['password_confirmation']);
|
|
|
|
$this->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users')
|
|
->assertSessionHasErrors('password');
|
|
});
|
|
|
|
test('birthday must be a valid date while creating a user', function () {
|
|
$this->beAdmin();
|
|
|
|
$data = validUserParams(['birthday' => 'foo']);
|
|
|
|
$this->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users')
|
|
->assertSessionHasErrors('birthday');
|
|
});
|
|
|
|
test('role field is required while creating a user', function () {
|
|
$this->beAdmin();
|
|
|
|
$data = validUserParams();
|
|
unset($data['role_id']);
|
|
|
|
$this->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users')
|
|
->assertSessionHasErrors('role_id');
|
|
});
|
|
|
|
test('selected role must exist inside the system while creating a user', function () {
|
|
$this->beAdmin();
|
|
|
|
$data = validUserParams(['role_id' => 123]);
|
|
|
|
$this->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users')
|
|
->assertSessionHasErrors('role_id');
|
|
});
|
|
|
|
test('country id field is not required while creating a user', function () {
|
|
$data = validUserParams(['country_id' => 0]);
|
|
|
|
$this->actingAsAdmin()
|
|
->post('/users', $data)
|
|
->assertRedirect('/users');
|
|
|
|
$this->assertSessionHasSuccess('User created successfully.');
|
|
|
|
$user = User::where('email', $data['email'])->first();
|
|
|
|
$expected = Arr::except($data, ['password', 'password_confirmation']);
|
|
$expected['country_id'] = null;
|
|
|
|
$this->assertDatabaseHas('users', $expected);
|
|
$this->assertTrue(Hash::check('123123123', $user->password));
|
|
});
|
|
|
|
test('country id must exist inside the system while creating a user', function () {
|
|
$this->beAdmin();
|
|
|
|
$data = validUserParams(['country_id' => 12345]);
|
|
|
|
$this->from('/users')
|
|
->post('/users', $data)
|
|
->assertRedirect('/users')
|
|
->assertSessionHasErrors('country_id');
|
|
});
|
|
|
|
test('admin can view users profile', function () {
|
|
$user = UserFactory::user()->create();
|
|
|
|
$this->actingAsAdmin()->get("users/{$user->id}")->assertOk();
|
|
});
|
|
|
|
test('users without appropriate permissions cannot view profile for another user', function () {
|
|
$roleA = RoleFactory::create();
|
|
$roleB = RoleFactory::withPermissions('users.manage')->create();
|
|
|
|
$userA = UserFactory::role($roleA)->create();
|
|
$userB = UserFactory::role($roleB)->create();
|
|
|
|
$this->actingAs($userA)->get("users/{$userB->id}")->assertForbidden();
|
|
$this->actingAs($userB)->get("users/{$userA->id}")->assertOk();
|
|
});
|
|
|
|
test('update user details', function () {
|
|
Event::fake([
|
|
UpdatedByAdmin::class,
|
|
]);
|
|
|
|
$this->beAdmin();
|
|
|
|
$user = UserFactory::user()->create();
|
|
|
|
$data = validUserParams();
|
|
|
|
$this->from("users/{$user->id}/edit")
|
|
->put("/users/{$user->id}/update/details", $data)
|
|
->assertRedirect("users/{$user->id}/edit");
|
|
|
|
$expected = Arr::except($data, ['password', 'password_confirmation']);
|
|
|
|
$this->assertDatabaseHas('users', $expected + ['id' => $user->id]);
|
|
$this->assertSessionHasSuccess('User updated successfully.');
|
|
|
|
Event::assertDispatched(UpdatedByAdmin::class);
|
|
});
|
|
|
|
test('users without appropriate permissions cannot update other users', function () {
|
|
$roleA = RoleFactory::create();
|
|
$roleB = RoleFactory::withPermissions('users.manage')->create();
|
|
|
|
$userA = UserFactory::role($roleA)->create();
|
|
$userB = UserFactory::role($roleB)->create();
|
|
|
|
$data = validUserParams();
|
|
|
|
$this->actingAs($userA)
|
|
->from("users/{$userB->id}/edit")
|
|
->put("/users/{$userB->id}/update/details", $data)
|
|
->assertForbidden();
|
|
|
|
$this->assertNotEquals($data['email'], $userB->fresh()->email);
|
|
|
|
$this->actingAs($userB)
|
|
->from("users/{$userA->id}/edit")
|
|
->put("/users/{$userA->id}/update/details", $data)
|
|
->assertRedirect("users/{$userA->id}/edit");
|
|
|
|
$this->assertEquals($data['email'], $userA->fresh()->email);
|
|
});
|
|
|
|
test('banning a user will invalidate all his sessions', function () {
|
|
config(['session.driver' => 'database']);
|
|
|
|
$this->beAdmin();
|
|
|
|
$user = UserFactory::user()->create(['remember_token' => Str::random(60)]);
|
|
|
|
DB::table('sessions')->insert([
|
|
'id' => Str::random(40),
|
|
'user_id' => $user->id,
|
|
'ip_address' => '127.0.0.1',
|
|
'user_agent' => 'Foo',
|
|
'payload' => Str::random(),
|
|
'last_activity' => Carbon::now()->subMinute()->timestamp,
|
|
]);
|
|
|
|
$data = validUserParams(['status' => UserStatus::BANNED->value]);
|
|
|
|
$this->put("/users/{$user->id}/update/details", $data);
|
|
|
|
$this->assertDatabaseMissing('sessions', ['user_id' => $user->id]);
|
|
$this->assertNull($user->fresh()->remember_token);
|
|
});
|
|
|
|
test('admin can update users login details', function () {
|
|
$this->beAdmin();
|
|
|
|
$user = UserFactory::user()->create();
|
|
|
|
$data = [
|
|
'email' => 'john@doe.com',
|
|
'username' => 'milos',
|
|
'password' => '123123123',
|
|
'password_confirmation' => '123123123',
|
|
];
|
|
|
|
$this->put("users/{$user->id}/update/login-details", $data)
|
|
->assertRedirect("users/{$user->id}/edit");
|
|
|
|
$this->assertSessionHasSuccess('Login details updated successfully.');
|
|
|
|
$user = $user->fresh();
|
|
|
|
$this->assertEquals($data['email'], $user->email);
|
|
$this->assertEquals($data['username'], $user->username);
|
|
$this->assertTrue(Hash::check($data['password'], $user->password));
|
|
});
|
|
|
|
test('users without appropriate permissions cannot update other users login details', function () {
|
|
$roleA = RoleFactory::create();
|
|
$roleB = RoleFactory::withPermissions('users.manage')->create();
|
|
|
|
$userA = UserFactory::role($roleA)->create();
|
|
$userB = UserFactory::role($roleB)->create();
|
|
|
|
$data = [
|
|
'email' => 'john@doe.com',
|
|
'username' => 'milos',
|
|
'password' => '123123123',
|
|
'password_confirmation' => '123123123',
|
|
];
|
|
|
|
$this->actingAs($userA)
|
|
->from("users/{$userB->id}/edit")
|
|
->put("users/{$userB->id}/update/login-details", $data)
|
|
->assertForbidden();
|
|
|
|
$this->assertNotEquals($data['email'], $userB->fresh()->email);
|
|
|
|
$this->actingAs($userB)
|
|
->from("users/{$userA->id}/edit")
|
|
->put("users/{$userA->id}/update/login-details", $data)
|
|
->assertRedirect("users/{$userA->id}/edit");
|
|
|
|
$this->assertEquals($data['email'], $userA->fresh()->email);
|
|
});
|
|
|
|
test('two factor form visibility', function () {
|
|
config(['services.authy.key' => 'test']);
|
|
|
|
$this->setSettings(['2fa.enabled' => false]);
|
|
|
|
$user = UserFactory::user()->create();
|
|
|
|
// The tab is rendered client side from the shared setting, so that is
|
|
// what decides whether it appears rather than the markup.
|
|
$this->assertFalse(usersTwoFactorSetting("users/{$user->id}/edit"));
|
|
|
|
$this->setSettings(['2fa.enabled' => true]);
|
|
|
|
$this->assertTrue(usersTwoFactorSetting("users/{$user->id}/edit"));
|
|
});
|
|
|
|
function usersTwoFactorSetting(string $url): bool
|
|
{
|
|
return (bool) test()->actingAsAdmin()
|
|
->get($url)
|
|
->viewData('page')['props']['settings']['2fa.enabled'];
|
|
}
|
|
|
|
test('admin can update avatar on behalf of a user', function () {
|
|
Storage::fake('public');
|
|
|
|
$data = [
|
|
'avatar' => UploadedFile::fake()->image('photo1.jpg', 300, 300),
|
|
'points' => [
|
|
'x1' => 0,
|
|
'y1' => 0,
|
|
'x2' => 200,
|
|
'y2' => 200,
|
|
],
|
|
];
|
|
|
|
$user = UserFactory::user()->create();
|
|
|
|
$this->actingAsAdmin()
|
|
->from("users/{$user->id}/edit")
|
|
->post("users/{$user->id}/update/avatar", $data)
|
|
->assertRedirect("users/{$user->id}/edit");
|
|
|
|
$this->assertSessionHasSuccess('Avatar changed successfully.');
|
|
|
|
$user->refresh();
|
|
|
|
$this->assertNotNull($user->avatar);
|
|
Storage::disk('public')->assertExists("upload/users/{$user->avatar}");
|
|
|
|
[$width, $height] = getimagesizefromstring(
|
|
Storage::disk('public')->get("upload/users/{$user->avatar}")
|
|
);
|
|
|
|
$this->assertEquals(160, $width);
|
|
$this->assertEquals(160, $height);
|
|
});
|
|
|
|
test('admin can update avatar on behalf of a user only if valid file is selected', function () {
|
|
Storage::fake('public');
|
|
|
|
$data = [
|
|
'avatar' => UploadedFile::fake()->create('foo.txt', 123),
|
|
'points' => [
|
|
'x1' => 0,
|
|
'y1' => 0,
|
|
'x2' => 200,
|
|
'y2' => 200,
|
|
],
|
|
];
|
|
|
|
$user = UserFactory::user()->create();
|
|
|
|
$this->actingAsAdmin()
|
|
->from("users/{$user->id}/edit")
|
|
->post("users/{$user->id}/update/avatar", $data)
|
|
->assertRedirect("users/{$user->id}/edit");
|
|
|
|
$this->assertNull($user->fresh()->avatar);
|
|
});
|
|
|
|
test('users without appropriate permissions cannot update avatar for other users', function () {
|
|
Storage::fake('public');
|
|
|
|
$roleA = RoleFactory::create();
|
|
$roleB = RoleFactory::withPermissions('users.manage')->create();
|
|
|
|
$userA = UserFactory::role($roleA)->create();
|
|
$userB = UserFactory::role($roleB)->create();
|
|
|
|
$data = [
|
|
'avatar' => UploadedFile::fake()->image('photo1.jpg', 300, 300),
|
|
'points' => [
|
|
'x1' => 0,
|
|
'y1' => 0,
|
|
'x2' => 200,
|
|
'y2' => 200,
|
|
],
|
|
];
|
|
|
|
$this->actingAs($userA)
|
|
->from("users/{$userB->id}/edit")
|
|
->post("users/{$userB->id}/update/avatar", $data)
|
|
->assertForbidden();
|
|
|
|
$this->assertNull($userB->fresh()->avatar);
|
|
|
|
$this->actingAs($userB)
|
|
->from("users/{$userA->id}/edit")
|
|
->post("users/{$userA->id}/update/avatar", $data)
|
|
->assertRedirect("users/{$userA->id}/edit");
|
|
|
|
$this->assertNotNull($userA->fresh()->avatar);
|
|
});
|
|
|
|
test('session page is not available for non database driver', function () {
|
|
config(['session.driver' => 'array']);
|
|
|
|
$user = UserFactory::admin()->create();
|
|
|
|
$this->actingAs($user)
|
|
->get('users')
|
|
->assertDontSee('User Sessions');
|
|
|
|
// this page should not be accessible if
|
|
// database session driver is not being used
|
|
$this->get("users/{$user->id}/sessions")->assertNotFound();
|
|
});
|
|
|
|
test('invalidate session', function () {
|
|
config(['session.driver' => 'database']);
|
|
|
|
Carbon::setTestNow(Carbon::now());
|
|
|
|
$user = UserFactory::admin()->withCredentials('foo', 'bar')->create();
|
|
|
|
$agent = $this->app['agent'];
|
|
$device = $agent->device() ?: 'Unknown';
|
|
$platform = $agent->platform() ?: 'Unknown';
|
|
$browser = $agent->browser() ?: 'Unknown';
|
|
|
|
// Log-in manually to actually create session record in DB
|
|
$this->post('/login', ['username' => 'foo', 'password' => 'bar']);
|
|
|
|
$sessions = $this->get("users/{$user->id}/sessions")
|
|
->viewData('page')['props']['sessions'];
|
|
|
|
$this->assertCount(1, $sessions);
|
|
$this->assertSame('127.0.0.1', $sessions[0]['ipAddress']);
|
|
$this->assertSame($device, $sessions[0]['device']);
|
|
$this->assertSame($platform, $sessions[0]['platform']);
|
|
$this->assertSame($browser, $sessions[0]['browser']);
|
|
|
|
$sessionId = DB::table('sessions')->where('user_id', $user->id)->first()->id;
|
|
$this->delete("users/{$user->id}/sessions/{$sessionId}/invalidate");
|
|
|
|
$this->assertDatabaseMissing('sessions', ['user_id' => $user->id]);
|
|
});
|
|
|
|
test('admins can delete other users', function () {
|
|
$user = UserFactory::user()->create();
|
|
|
|
$this->actingAsAdmin()
|
|
->delete(route('users.destroy', $user))
|
|
->assertRedirect('users');
|
|
|
|
$this->assertSessionHasSuccess('User deleted successfully.');
|
|
|
|
$this->assertNull($user->fresh());
|
|
});
|
|
|
|
test('users without appropriate permissions cannot delete other users', function () {
|
|
$roleA = RoleFactory::create();
|
|
$roleB = RoleFactory::withPermissions('users.manage')->create();
|
|
|
|
$userA = UserFactory::role($roleA)->create();
|
|
$userB = UserFactory::role($roleB)->create();
|
|
|
|
$this->actingAs($userA)
|
|
->delete(route('users.destroy', $userB))
|
|
->assertForbidden();
|
|
|
|
$this->assertNotNull($userB->fresh());
|
|
|
|
$this->actingAs($userB)
|
|
->delete(route('users.destroy', $userA))
|
|
->assertRedirect('/users');
|
|
|
|
$this->assertNull($userA->fresh());
|
|
});
|
|
|
|
test('user cannot delete himself', function () {
|
|
$this->beAdmin();
|
|
|
|
$this->delete(route('users.destroy', auth()->id()))
|
|
->assertRedirect('/users');
|
|
|
|
$this->assertNotNull(auth()->user()->fresh());
|
|
$this->assertSessionHasError('You cannot delete yourself.');
|
|
});
|
|
|
|
function validUserParams(array $overrides = []): array
|
|
{
|
|
return array_merge([
|
|
'role_id' => Role::whereName('User')->first()->id,
|
|
'status' => UserStatus::ACTIVE->value,
|
|
'first_name' => 'foo',
|
|
'last_name' => 'bar',
|
|
'birthday' => Carbon::now()->subYears(25)->format('Y-m-d'),
|
|
'phone' => '12345667',
|
|
'address' => 'the address',
|
|
'country_id' => 688, // Serbia,
|
|
'email' => 'john@doe.com',
|
|
'username' => 'johndoe',
|
|
'password' => '123123123',
|
|
'password_confirmation' => '123123123',
|
|
], $overrides);
|
|
}
|