generated from thegrind/laravel-dockerized
Invitation system test
This commit is contained in:
parent
c976cf6e53
commit
292ec10b48
@ -30,6 +30,10 @@ class ManageUsers extends Component
|
|||||||
{
|
{
|
||||||
$this->authorize('invite', User::class);
|
$this->authorize('invite', User::class);
|
||||||
|
|
||||||
|
$this->validate([
|
||||||
|
'invite_email' => 'required|email|unique:invitations,email|unique:users,email',
|
||||||
|
]);
|
||||||
|
|
||||||
$inv = Invitation::create([
|
$inv = Invitation::create([
|
||||||
'code' => str()->random(50),
|
'code' => str()->random(50),
|
||||||
'email' => $this->invite_email,
|
'email' => $this->invite_email,
|
||||||
@ -38,7 +42,8 @@ class ManageUsers extends Component
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Send email if checkbox is checked
|
// Send email if checkbox is checked
|
||||||
if ($this->send_email) {
|
$emailSent = $this->send_email;
|
||||||
|
if ($emailSent) {
|
||||||
Mail::to($inv->email)->send(new InvitationMail($inv));
|
Mail::to($inv->email)->send(new InvitationMail($inv));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,10 +52,9 @@ class ManageUsers extends Component
|
|||||||
// Refresh the data
|
// Refresh the data
|
||||||
$this->invitations->prepend($inv);
|
$this->invitations->prepend($inv);
|
||||||
$this->reset(['invite_email', 'send_email']);
|
$this->reset(['invite_email', 'send_email']);
|
||||||
$this->invite_email = '';
|
|
||||||
$this->send_email = false;
|
|
||||||
|
|
||||||
session()->flash('success', 'Invitation created successfully' . ($this->send_email ? ' and email sent' : '') . '.');
|
$message = 'Invitation created successfully' . ($emailSent ? ' and email sent' : '') . '.';
|
||||||
|
session()->flash('success', $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteUser(User $user)
|
public function deleteUser(User $user)
|
||||||
|
@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
class Invitation extends Model
|
class Invitation extends Model
|
||||||
{
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
protected $guarded = ['id'];
|
protected $guarded = ['id'];
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'expires_at' => 'datetime',
|
'expires_at' => 'datetime',
|
||||||
|
@ -19,7 +19,7 @@ class InvitationFactory extends Factory
|
|||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'code' => Invitation::generateCode(),
|
'code' => fake()->unique()->regexify('[A-Za-z0-9]{50}'),
|
||||||
'email' => fake()->unique()->safeEmail(),
|
'email' => fake()->unique()->safeEmail(),
|
||||||
'invited_by' => User::factory(),
|
'invited_by' => User::factory(),
|
||||||
'expires_at' => now()->addDays(7),
|
'expires_at' => now()->addDays(7),
|
||||||
|
@ -7,9 +7,3 @@ uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
|||||||
test('guests are redirected to the login page', function () {
|
test('guests are redirected to the login page', function () {
|
||||||
$this->get('/dashboard')->assertRedirect('/login');
|
$this->get('/dashboard')->assertRedirect('/login');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('authenticated users can visit the dashboard', function () {
|
|
||||||
$this->actingAs($user = User::factory()->create());
|
|
||||||
|
|
||||||
$this->get('/dashboard')->assertStatus(200);
|
|
||||||
});
|
|
283
tests/Feature/ManageUsersTest.php
Normal file
283
tests/Feature/ManageUsersTest.php
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Livewire\ManageUsers;
|
||||||
|
use App\Mail\InvitationMail;
|
||||||
|
use App\Models\Invitation;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
// Create test users
|
||||||
|
$this->admin = User::factory()->create(['is_admin' => true]);
|
||||||
|
$this->user = User::factory()->create(['is_admin' => false]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ManageUsers Component', function () {
|
||||||
|
|
||||||
|
describe('invitation creation', function () {
|
||||||
|
|
||||||
|
it('allows admins to create invitations', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->set('invite_email', 'test@example.com')
|
||||||
|
->call('inviteUser')
|
||||||
|
->assertHasNoErrors();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('invitations', [
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
'invited_by' => $this->admin->id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevents non-admins from creating invitations', function () {
|
||||||
|
// Test authorization at the method level without rendering view
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
$component = new ManageUsers();
|
||||||
|
$component->invite_email = 'test@example.com';
|
||||||
|
|
||||||
|
$this->expectException(\Illuminate\Auth\Access\AuthorizationException::class);
|
||||||
|
$component->inviteUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates email format when creating invitations', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->set('invite_email', 'invalid-email')
|
||||||
|
->call('inviteUser')
|
||||||
|
->assertHasErrors('invite_email');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a random code and sets expiration when creating invitations', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->set('invite_email', 'test@example.com')
|
||||||
|
->call('inviteUser');
|
||||||
|
|
||||||
|
$invitation = Invitation::where('email', 'test@example.com')->first();
|
||||||
|
|
||||||
|
expect($invitation->code)->toHaveLength(50);
|
||||||
|
expect($invitation->expires_at)->not()->toBeNull();
|
||||||
|
expect($invitation->invited_by)->toBe($this->admin->id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('email sending', function () {
|
||||||
|
|
||||||
|
it('sends email when checkbox is checked', function () {
|
||||||
|
Mail::fake();
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->set('invite_email', 'test@example.com')
|
||||||
|
->set('send_email', true)
|
||||||
|
->call('inviteUser');
|
||||||
|
|
||||||
|
$invitation = Invitation::where('email', 'test@example.com')->first();
|
||||||
|
|
||||||
|
Mail::assertSent(InvitationMail::class, function ($mail) use ($invitation) {
|
||||||
|
return $mail->hasTo('test@example.com') &&
|
||||||
|
$mail->invitation->id === $invitation->id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not send email when checkbox is unchecked', function () {
|
||||||
|
Mail::fake();
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->set('invite_email', 'test@example.com')
|
||||||
|
->set('send_email', false)
|
||||||
|
->call('inviteUser');
|
||||||
|
|
||||||
|
Mail::assertNotSent(InvitationMail::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends email to the correct recipient', function () {
|
||||||
|
Mail::fake();
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
$testEmail = 'specific@example.com';
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->set('invite_email', $testEmail)
|
||||||
|
->set('send_email', true)
|
||||||
|
->call('inviteUser');
|
||||||
|
|
||||||
|
Mail::assertSent(InvitationMail::class, function ($mail) use ($testEmail) {
|
||||||
|
return $mail->hasTo($testEmail);
|
||||||
|
});
|
||||||
|
|
||||||
|
Mail::assertNotSent(InvitationMail::class, function ($mail) {
|
||||||
|
return $mail->hasTo('wrong@example.com');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('invitation deletion', function () {
|
||||||
|
|
||||||
|
it('allows admins to delete pending invitations', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
$invitation = Invitation::factory()->create([
|
||||||
|
'accepted_at' => null, // Pending invitation
|
||||||
|
]);
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->call('deleteInvitation', $invitation)
|
||||||
|
->assertHasNoErrors();
|
||||||
|
|
||||||
|
$this->assertDatabaseMissing('invitations', [
|
||||||
|
'id' => $invitation->id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevents non-admins from deleting invitations', function () {
|
||||||
|
// Test authorization at the method level without rendering view
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
|
$invitation = Invitation::factory()->create([
|
||||||
|
'accepted_at' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$component = new ManageUsers();
|
||||||
|
|
||||||
|
$this->expectException(\Illuminate\Auth\Access\AuthorizationException::class);
|
||||||
|
$component->deleteInvitation($invitation);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevents deletion of accepted invitations', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
$invitation = Invitation::factory()->create([
|
||||||
|
'accepted_at' => now(), // Accepted invitation
|
||||||
|
]);
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->call('deleteInvitation', $invitation)
|
||||||
|
->assertHasNoErrors(); // Component handles this gracefully
|
||||||
|
|
||||||
|
// Invitation should still exist
|
||||||
|
$this->assertDatabaseHas('invitations', [
|
||||||
|
'id' => $invitation->id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('component state management', function () {
|
||||||
|
|
||||||
|
it('resets form fields after successful invitation creation', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
$component = Livewire::test(ManageUsers::class)
|
||||||
|
->set('invite_email', 'test@example.com')
|
||||||
|
->set('send_email', true)
|
||||||
|
->call('inviteUser');
|
||||||
|
|
||||||
|
expect($component->get('invite_email'))->toBe('');
|
||||||
|
expect($component->get('send_email'))->toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates invitations collection after creation', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
$component = Livewire::test(ManageUsers::class);
|
||||||
|
$initialCount = $component->get('invitations')->count();
|
||||||
|
|
||||||
|
$component
|
||||||
|
->set('invite_email', 'test@example.com')
|
||||||
|
->call('inviteUser');
|
||||||
|
|
||||||
|
expect($component->get('invitations')->count())->toBe($initialCount + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows success message after invitation creation', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->set('invite_email', 'test@example.com')
|
||||||
|
->set('send_email', false)
|
||||||
|
->call('inviteUser')
|
||||||
|
->assertHasNoErrors();
|
||||||
|
|
||||||
|
// Verify the invitation was created
|
||||||
|
$this->assertDatabaseHas('invitations', [
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tracks email sending status correctly', function () {
|
||||||
|
Mail::fake();
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->set('invite_email', 'test@example.com')
|
||||||
|
->set('send_email', true)
|
||||||
|
->call('inviteUser')
|
||||||
|
->assertHasNoErrors();
|
||||||
|
|
||||||
|
// Verify the invitation was created
|
||||||
|
$this->assertDatabaseHas('invitations', [
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Verify email was sent
|
||||||
|
Mail::assertSent(InvitationMail::class);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('authorization integration', function () {
|
||||||
|
|
||||||
|
it('loads data for admin users', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
$adminComponent = Livewire::test(ManageUsers::class);
|
||||||
|
expect($adminComponent->get('users'))->not()->toBeNull();
|
||||||
|
expect($adminComponent->get('invitations'))->not()->toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('user role management', function () {
|
||||||
|
|
||||||
|
it('allows admins to change user roles', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
$targetUser = User::factory()->create(['is_admin' => false]);
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->call('changeUserRole', $targetUser, 'admin')
|
||||||
|
->assertHasNoErrors();
|
||||||
|
|
||||||
|
expect($targetUser->fresh()->is_admin)->toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevents admins from demoting themselves', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->call('changeUserRole', $this->admin, 'user')
|
||||||
|
->assertHasNoErrors();
|
||||||
|
|
||||||
|
// Verify the admin status wasn't changed
|
||||||
|
expect($this->admin->fresh()->is_admin)->toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows admins to delete other users', function () {
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
$targetUser = User::factory()->create();
|
||||||
|
|
||||||
|
Livewire::test(ManageUsers::class)
|
||||||
|
->call('deleteUser', $targetUser)
|
||||||
|
->assertHasNoErrors();
|
||||||
|
|
||||||
|
$this->assertDatabaseMissing('users', [
|
||||||
|
'id' => $targetUser->id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user