ledgerrz/tests/Feature/InvitationTest.php
Daan Meijer 3c414e1ef1
Some checks failed
linter / quality (push) Failing after 1m3s
tests / ci (8.3) (push) Failing after 48s
tests / ci (8.4) (push) Failing after 1m5s
tests / ci (8.5) (push) Failing after 1m3s
refactoring of system messages
2026-06-17 00:57:43 +02:00

104 lines
3.7 KiB
PHP

<?php
use App\Models\User;
use App\Models\Dynamic;
use App\Models\DynamicInvitation;
use App\Mail\DynamicInvitationMail;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\URL;
test('only owners can invite other users to a dynamic', function () {
Mail::fake();
$owner = User::factory()->create();
$participant = User::factory()->create();
$dynamic = Dynamic::factory()->create();
$dynamic->participants()->attach($owner->id, ['role' => 'owner']);
$dynamic->participants()->attach($participant->id, ['role' => 'participant']);
// 1. Participant tries to send an invite (forbidden)
$response = $this->actingAs($participant)
->post(route('dynamics.invitations.store', $dynamic), [
'email' => 'invitee@example.com',
'role' => 'participant',
]);
$response->assertStatus(403);
Mail::assertNothingSent();
// 2. Owner sends a valid invite (allowed)
$response = $this->actingAs($owner)
->post(route('dynamics.invitations.store', $dynamic), [
'email' => 'invitee@example.com',
'role' => 'participant',
]);
$response->assertRedirect();
$response->assertSessionHasNoErrors();
// Verify invitation is stored
$invitation = DynamicInvitation::firstWhere('email', 'invitee@example.com');
expect($invitation)->not->toBeNull();
expect($invitation->role)->toBe('participant');
expect($invitation->dynamic_id)->toBe($dynamic->id);
// Verify email was dispatched
Mail::assertSent(DynamicInvitationMail::class, function ($mail) use ($invitation) {
return $mail->hasTo($invitation->email);
});
});
test('only the user with the specified email address can accept the link', function () {
$owner = User::factory()->create();
$invitee = User::factory()->create(['email' => 'matching@example.com']);
$hijacker = User::factory()->create(['email' => 'hijacker@example.com']);
$dynamic = Dynamic::factory()->create();
$dynamic->participants()->attach($owner->id, ['role' => 'owner']);
// Create a pending invitation
$invitation = $dynamic->invitations()->create([
'email' => 'matching@example.com',
'role' => 'editor',
'token' => 'secure_test_token_123',
'expires_at' => now()->addDays(7),
]);
// Generate a secure, valid signed URL
$signedUrl = URL::temporarySignedRoute(
'dynamics.invitations.accept',
$invitation->expires_at,
['token' => $invitation->token]
);
// 1. Unauthenticated user tries to accept (redirected to login)
$response = $this->get($signedUrl);
$response->assertRedirect('/login');
// 2. Different user (hijacker) tries to accept (forbidden / 403)
$response = $this->actingAs($hijacker)->get($signedUrl);
$response->assertStatus(403);
expect($dynamic->participants()->where('user_id', $hijacker->id)->exists())->toBeFalse();
// 3. Intended user accepts the signed invitation link (success)
$response = $this->actingAs($invitee)->get($signedUrl);
$response->assertRedirect(route('dynamics.show', $dynamic));
// Verify invitee is joined as a participant with the specified role
$isJoined = $dynamic->participants()
->where('user_id', $invitee->id)
->wherePivot('role', 'editor')
->exists();
expect($isJoined)->toBeTrue();
// Verify invitation is deleted from the database
expect(DynamicInvitation::where('token', 'secure_test_token_123')->exists())->toBeFalse();
// Verify system notification is added to Dynamic activity chat
$chatMessages = $dynamic->chat->messages;
expect($chatMessages)->not->toBeEmpty();
expect($chatMessages->last()->content)->toBe("{$invitee->name} joined the Dynamic as a EDITOR");
});