feat: add Reverb realtime messaging
This commit is contained in:
@@ -8,8 +8,13 @@ use App\Models\Message;
|
||||
use App\Models\MessageAttachment;
|
||||
use App\Models\Report;
|
||||
use App\Models\User;
|
||||
use App\Policies\ConversationPolicy;
|
||||
use App\Events\ConversationUpdated;
|
||||
use App\Events\MessageCreated;
|
||||
use App\Events\MessageRead;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
@@ -150,6 +155,110 @@ test('pin and unpin endpoints toggle participant pin state', function () {
|
||||
->assertJsonFragment(['is_pinned' => false]);
|
||||
});
|
||||
|
||||
test('sending a message dispatches realtime events and preserves client temp id', function () {
|
||||
Event::fake([MessageCreated::class, ConversationUpdated::class]);
|
||||
|
||||
$userA = makeMessagingUser();
|
||||
$userB = makeMessagingUser();
|
||||
$conv = makeDirectConversation($userA, $userB);
|
||||
|
||||
$response = $this->actingAs($userA)->postJson("/api/messages/{$conv->id}", [
|
||||
'body' => 'Realtime hello',
|
||||
'client_temp_id' => 'tmp_feature_test_123',
|
||||
]);
|
||||
|
||||
$response->assertStatus(201)
|
||||
->assertJsonFragment(['client_temp_id' => 'tmp_feature_test_123'])
|
||||
->assertJsonFragment(['body' => 'Realtime hello']);
|
||||
|
||||
Event::assertDispatched(MessageCreated::class);
|
||||
Event::assertDispatched(ConversationUpdated::class);
|
||||
});
|
||||
|
||||
test('non participant cannot send a message', function () {
|
||||
$userA = makeMessagingUser();
|
||||
$userB = makeMessagingUser();
|
||||
$outsider = makeMessagingUser();
|
||||
$conv = makeDirectConversation($userA, $userB);
|
||||
|
||||
$this->actingAs($outsider)->postJson("/api/messages/{$conv->id}", [
|
||||
'body' => 'intrusion',
|
||||
])->assertStatus(403);
|
||||
});
|
||||
|
||||
test('channel authorization denies non participant and allows participant', function () {
|
||||
$userA = makeMessagingUser();
|
||||
$userB = makeMessagingUser();
|
||||
$outsider = makeMessagingUser();
|
||||
$conv = makeDirectConversation($userA, $userB);
|
||||
|
||||
$policy = app(ConversationPolicy::class);
|
||||
|
||||
expect($policy->view($outsider, $conv))->toBeFalse()
|
||||
->and($policy->view($userA, $conv))->toBeTrue()
|
||||
->and($policy->joinPresence($outsider, $conv))->toBeFalse()
|
||||
->and($policy->joinPresence($userB, $conv))->toBeTrue();
|
||||
});
|
||||
|
||||
test('mark read updates last read message id and dispatches read event', function () {
|
||||
Event::fake([MessageRead::class, ConversationUpdated::class]);
|
||||
|
||||
$userA = makeMessagingUser();
|
||||
$userB = makeMessagingUser();
|
||||
$conv = makeDirectConversation($userA, $userB);
|
||||
|
||||
$first = Message::create([
|
||||
'conversation_id' => $conv->id,
|
||||
'sender_id' => $userB->id,
|
||||
'body' => 'One',
|
||||
]);
|
||||
|
||||
$last = Message::create([
|
||||
'conversation_id' => $conv->id,
|
||||
'sender_id' => $userB->id,
|
||||
'body' => 'Two',
|
||||
]);
|
||||
|
||||
$this->actingAs($userA)
|
||||
->postJson("/api/messages/{$conv->id}/read", ['message_id' => $last->id])
|
||||
->assertStatus(200)
|
||||
->assertJsonFragment(['last_read_message_id' => $last->id]);
|
||||
|
||||
$participant = ConversationParticipant::query()
|
||||
->where('conversation_id', $conv->id)
|
||||
->where('user_id', $userA->id)
|
||||
->firstOrFail();
|
||||
|
||||
expect($participant->last_read_message_id)->toBe($last->id);
|
||||
|
||||
$this->assertDatabaseHas('message_reads', [
|
||||
'message_id' => $first->id,
|
||||
'user_id' => $userA->id,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('message_reads', [
|
||||
'message_id' => $last->id,
|
||||
'user_id' => $userA->id,
|
||||
]);
|
||||
|
||||
Event::assertDispatched(MessageRead::class);
|
||||
});
|
||||
|
||||
test('typing endpoints reject non participants', function () {
|
||||
$userA = makeMessagingUser();
|
||||
$userB = makeMessagingUser();
|
||||
$outsider = makeMessagingUser();
|
||||
$conv = makeDirectConversation($userA, $userB);
|
||||
|
||||
$this->actingAs($outsider)
|
||||
->postJson("/api/messages/{$conv->id}/typing")
|
||||
->assertStatus(403);
|
||||
|
||||
$this->actingAs($outsider)
|
||||
->postJson("/api/messages/{$conv->id}/typing/stop")
|
||||
->assertStatus(403);
|
||||
});
|
||||
|
||||
test('report endpoint creates moderation report entry', function () {
|
||||
$userA = makeMessagingUser();
|
||||
$userB = makeMessagingUser();
|
||||
|
||||
Reference in New Issue
Block a user