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->validate([
|
||||
'invite_email' => 'required|email|unique:invitations,email|unique:users,email',
|
||||
]);
|
||||
|
||||
$inv = Invitation::create([
|
||||
'code' => str()->random(50),
|
||||
'email' => $this->invite_email,
|
||||
@ -38,7 +42,8 @@ class ManageUsers extends Component
|
||||
]);
|
||||
|
||||
// Send email if checkbox is checked
|
||||
if ($this->send_email) {
|
||||
$emailSent = $this->send_email;
|
||||
if ($emailSent) {
|
||||
Mail::to($inv->email)->send(new InvitationMail($inv));
|
||||
}
|
||||
|
||||
@ -47,10 +52,9 @@ class ManageUsers extends Component
|
||||
// Refresh the data
|
||||
$this->invitations->prepend($inv);
|
||||
$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)
|
||||
|
@ -2,11 +2,14 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Invitation extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
protected $casts = [
|
||||
'expires_at' => 'datetime',
|
||||
|
@ -19,7 +19,7 @@ class InvitationFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'code' => Invitation::generateCode(),
|
||||
'code' => fake()->unique()->regexify('[A-Za-z0-9]{50}'),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'invited_by' => User::factory(),
|
||||
'expires_at' => now()->addDays(7),
|
||||
|
@ -7,9 +7,3 @@ uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||
test('guests are redirected to the login page', function () {
|
||||
$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