Deploy Ladill Meet / deploy (push) Successful in 52s
Guests were sent through silent SSO after entering the meeting room because /room routes required auth; failed SSO fell back to leftover Care config. Co-authored-by: Cursor <cursoragent@cursor.com>
439 lines
14 KiB
PHP
439 lines
14 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Http\Middleware\EnsurePlatformSession;
|
|
use App\Models\Member;
|
|
use App\Models\Organization;
|
|
use App\Models\Recording;
|
|
use App\Models\Room;
|
|
use App\Models\Session;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
class MeetWebTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected User $user;
|
|
|
|
protected Organization $organization;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->withoutMiddleware(EnsurePlatformSession::class);
|
|
|
|
$this->user = User::create([
|
|
'public_id' => 'test-user-001',
|
|
'name' => 'Test User',
|
|
'email' => 'test@example.com',
|
|
]);
|
|
|
|
$this->organization = Organization::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'name' => 'Test Org',
|
|
'slug' => 'test-org',
|
|
'timezone' => 'UTC',
|
|
'settings' => ['onboarded' => true],
|
|
]);
|
|
|
|
Member::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'user_ref' => $this->user->public_id,
|
|
'role' => 'owner',
|
|
]);
|
|
}
|
|
|
|
public function test_health_endpoint(): void
|
|
{
|
|
$this->getJson('/api/health')
|
|
->assertOk()
|
|
->assertJson(['status' => 'ok', 'app' => 'meet']);
|
|
}
|
|
|
|
public function test_guest_redirected_from_dashboard(): void
|
|
{
|
|
$this->get('/dashboard')->assertRedirect();
|
|
}
|
|
|
|
public function test_onboarded_user_can_view_dashboard(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->get('/dashboard')
|
|
->assertOk();
|
|
}
|
|
|
|
public function test_user_can_create_instant_meeting(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->get('/meetings/instant')
|
|
->assertRedirect();
|
|
|
|
$this->assertDatabaseHas('meet_rooms', [
|
|
'host_user_ref' => $this->user->public_id,
|
|
'type' => 'instant',
|
|
]);
|
|
}
|
|
|
|
public function test_instant_meeting_opens_room(): void
|
|
{
|
|
$response = $this->actingAs($this->user)
|
|
->get('/meetings/instant');
|
|
|
|
$response->assertRedirect();
|
|
|
|
$session = Session::where('owner_ref', $this->user->public_id)->latest()->first();
|
|
$this->assertNotNull($session);
|
|
$this->assertTrue($session->isLive());
|
|
|
|
$this->actingAs($this->user)
|
|
->get('/room/'.$session->uuid)
|
|
->assertOk()
|
|
->assertSee($session->room->title);
|
|
}
|
|
|
|
public function test_host_can_start_scheduled_meeting_into_room(): void
|
|
{
|
|
$room = $this->createRoom();
|
|
|
|
$this->actingAs($this->user)
|
|
->post('/meetings/'.$room->uuid.'/start')
|
|
->assertRedirect();
|
|
|
|
$session = $room->fresh()->activeSession();
|
|
$this->assertNotNull($session);
|
|
|
|
$this->actingAs($this->user)
|
|
->get('/room/'.$session->uuid)
|
|
->assertOk();
|
|
}
|
|
|
|
public function test_guest_cannot_join_before_host_starts(): void
|
|
{
|
|
$room = $this->createRoom([
|
|
'settings' => array_merge(config('meet.default_settings'), [
|
|
'join_before_host' => false,
|
|
]),
|
|
]);
|
|
|
|
$this->post('/r/'.$room->uuid.'/enter', [
|
|
'display_name' => 'Guest User',
|
|
])
|
|
->assertRedirect()
|
|
->assertSessionHasErrors('join');
|
|
}
|
|
|
|
public function test_guest_can_join_live_meeting_and_enter_room(): void
|
|
{
|
|
$room = $this->createRoom([
|
|
'settings' => array_merge(config('meet.default_settings'), [
|
|
'join_before_host' => true,
|
|
]),
|
|
]);
|
|
$session = Session::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'room_id' => $room->id,
|
|
'media_room_name' => 'room-'.$room->uuid,
|
|
'status' => 'live',
|
|
'started_at' => now(),
|
|
]);
|
|
$room->update(['status' => 'live']);
|
|
|
|
$this->post('/r/'.$room->uuid.'/enter', [
|
|
'display_name' => 'Guest User',
|
|
])
|
|
->assertRedirect(route('meet.room', $session));
|
|
|
|
$this->get('/room/'.$session->uuid)
|
|
->assertOk()
|
|
->assertSee('data-display-name="Guest User"', false);
|
|
}
|
|
|
|
public function test_authenticated_user_can_join_live_meeting_without_display_name(): void
|
|
{
|
|
$room = $this->createRoom();
|
|
$session = Session::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'room_id' => $room->id,
|
|
'media_room_name' => 'room-'.$room->uuid,
|
|
'status' => 'live',
|
|
'started_at' => now(),
|
|
]);
|
|
$room->update(['status' => 'live']);
|
|
|
|
$this->actingAs($this->user)
|
|
->post('/r/'.$room->uuid.'/enter')
|
|
->assertRedirect(route('meet.room', $session));
|
|
|
|
$this->actingAs($this->user)
|
|
->get('/room/'.$session->uuid)
|
|
->assertOk();
|
|
}
|
|
|
|
public function test_room_has_join_url(): void
|
|
{
|
|
$room = Room::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'host_user_ref' => $this->user->public_id,
|
|
'title' => 'Test Meeting',
|
|
'type' => 'instant',
|
|
'status' => 'scheduled',
|
|
'scheduled_at' => now(),
|
|
'timezone' => 'UTC',
|
|
'settings' => config('meet.default_settings'),
|
|
]);
|
|
|
|
$this->assertStringContainsString('meet.ladill.com/r/'.$room->uuid, $room->joinUrl());
|
|
}
|
|
|
|
public function test_user_can_view_recordings_index(): void
|
|
{
|
|
$room = $this->createRoom();
|
|
$session = Session::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'room_id' => $room->id,
|
|
'media_room_name' => 'room-'.$room->uuid,
|
|
'status' => 'ended',
|
|
'started_at' => now()->subHour(),
|
|
'ended_at' => now(),
|
|
]);
|
|
Recording::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'session_id' => $session->id,
|
|
'status' => 'ready',
|
|
'storage_path' => 'recordings/test.mp4',
|
|
]);
|
|
|
|
$this->actingAs($this->user)
|
|
->get('/recordings')
|
|
->assertOk()
|
|
->assertSee('Recordings');
|
|
}
|
|
|
|
public function test_admin_can_create_template(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->post('/templates', [
|
|
'name' => 'Weekly standup',
|
|
'duration_minutes' => 30,
|
|
'waiting_room' => '1',
|
|
])
|
|
->assertRedirect(route('meet.templates.index'));
|
|
|
|
$this->assertDatabaseHas('meet_templates', [
|
|
'name' => 'Weekly standup',
|
|
'organization_id' => $this->organization->id,
|
|
]);
|
|
}
|
|
|
|
public function test_admin_can_update_security_policy(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->put('/settings/security', [
|
|
'org_only' => '1',
|
|
'authenticated_only' => '1',
|
|
'allowed_domains' => 'example.com, ladill.com',
|
|
'recording_host_only' => '1',
|
|
'session_idle_minutes' => 90,
|
|
])
|
|
->assertRedirect(route('meet.settings.security'));
|
|
|
|
$this->organization->refresh();
|
|
$this->assertTrue($this->organization->securitySetting('org_only'));
|
|
$this->assertContains('example.com', $this->organization->securitySetting('allowed_domains'));
|
|
}
|
|
|
|
public function test_user_can_access_personal_room(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->get('/meetings/personal')
|
|
->assertRedirect();
|
|
|
|
$this->assertDatabaseHas('meet_rooms', [
|
|
'host_user_ref' => $this->user->public_id,
|
|
'type' => 'personal',
|
|
]);
|
|
}
|
|
|
|
public function test_admin_can_view_reports(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->get('/reports')
|
|
->assertOk()
|
|
->assertSee('Reports');
|
|
}
|
|
|
|
public function test_service_api_can_create_room(): void
|
|
{
|
|
config(['meet.service_api_keys.care' => 'test-care-key']);
|
|
|
|
$this->postJson('/api/service/v1/rooms', [
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'host_user_ref' => $this->user->public_id,
|
|
'title' => 'Care consultation',
|
|
'source' => ['app' => 'care', 'entity_type' => 'appointment', 'entity_id' => 'appt-123'],
|
|
], ['Authorization' => 'Bearer test-care-key'])
|
|
->assertCreated()
|
|
->assertJsonPath('room.title', 'Care consultation');
|
|
|
|
$this->assertDatabaseHas('meet_rooms', [
|
|
'title' => 'Care consultation',
|
|
'host_user_ref' => $this->user->public_id,
|
|
]);
|
|
}
|
|
|
|
public function test_invitation_rsvp_accepts(): void
|
|
{
|
|
$room = $this->createRoom();
|
|
$invitation = \App\Models\Invitation::create([
|
|
'owner_ref' => $this->user->public_id,
|
|
'room_id' => $room->id,
|
|
'email' => 'guest@example.com',
|
|
'status' => 'pending',
|
|
'rsvp_token' => 'test-rsvp-token',
|
|
]);
|
|
|
|
$this->post('/invite/'.$invitation->uuid.'/rsvp?token=test-rsvp-token', [
|
|
'status' => 'accepted',
|
|
])->assertOk();
|
|
|
|
$this->assertDatabaseHas('meet_invitations', [
|
|
'id' => $invitation->id,
|
|
'status' => 'accepted',
|
|
]);
|
|
}
|
|
|
|
public function test_admin_can_add_webhook_endpoint(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->post('/settings/webhooks', [
|
|
'url' => 'https://care.example.com/webhooks/meet',
|
|
'secret' => 'whsec_test',
|
|
'events' => ['meeting.started', 'meeting.ended'],
|
|
])
|
|
->assertRedirect(route('meet.settings.webhooks'));
|
|
|
|
$this->assertDatabaseHas('meet_webhook_endpoints', [
|
|
'organization_id' => $this->organization->id,
|
|
'url' => 'https://care.example.com/webhooks/meet',
|
|
]);
|
|
}
|
|
|
|
public function test_webinar_registration_flow(): void
|
|
{
|
|
$room = $this->createRoom([
|
|
'type' => 'webinar',
|
|
'settings' => array_merge(config('meet.default_settings'), ['webinar_auto_approve' => true]),
|
|
]);
|
|
|
|
$this->post('/w/'.$room->uuid.'/register', [
|
|
'email' => 'attendee@example.com',
|
|
'display_name' => 'Attendee One',
|
|
])->assertOk();
|
|
|
|
$this->assertDatabaseHas('meet_webinar_registrations', [
|
|
'room_id' => $room->id,
|
|
'email' => 'attendee@example.com',
|
|
'status' => 'approved',
|
|
]);
|
|
}
|
|
|
|
public function test_host_can_create_webinar_room(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->post('/meetings', [
|
|
'title' => 'Product launch',
|
|
'type' => 'webinar',
|
|
'scheduled_at' => now()->addDay()->format('Y-m-d\TH:i'),
|
|
'duration_minutes' => 60,
|
|
])
|
|
->assertRedirect();
|
|
|
|
$this->assertDatabaseHas('meet_rooms', [
|
|
'title' => 'Product launch',
|
|
'type' => 'webinar',
|
|
]);
|
|
}
|
|
|
|
public function test_user_can_create_channel_and_post_message(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->post('/channels', [
|
|
'name' => 'General',
|
|
'visibility' => 'public',
|
|
])
|
|
->assertRedirect();
|
|
|
|
$channel = \App\Models\Channel::first();
|
|
$this->assertNotNull($channel);
|
|
|
|
$this->actingAs($this->user)
|
|
->post('/channels/'.$channel->uuid.'/messages', ['body' => 'Hello team'])
|
|
->assertRedirect();
|
|
|
|
$this->assertDatabaseHas('meet_channel_messages', [
|
|
'channel_id' => $channel->id,
|
|
'body' => 'Hello team',
|
|
]);
|
|
}
|
|
|
|
public function test_admin_can_view_usage_dashboard(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->get('/admin/usage')
|
|
->assertOk()
|
|
->assertSee('Usage');
|
|
}
|
|
|
|
public function test_mail_calendar_can_be_connected(): void
|
|
{
|
|
config(['meet.calendar.mail.api_key' => 'test-mail-key']);
|
|
|
|
$this->actingAs($this->user)
|
|
->post('/settings/calendar/mail')
|
|
->assertRedirect(route('meet.settings.calendar'));
|
|
|
|
$this->assertDatabaseHas('meet_calendar_connections', [
|
|
'user_ref' => $this->user->public_id,
|
|
'provider' => 'mail',
|
|
]);
|
|
}
|
|
|
|
public function test_host_can_create_town_hall_room(): void
|
|
{
|
|
$this->actingAs($this->user)
|
|
->post('/meetings', [
|
|
'title' => 'All hands',
|
|
'type' => 'town_hall',
|
|
'scheduled_at' => now()->addDay()->format('Y-m-d\TH:i'),
|
|
'duration_minutes' => 90,
|
|
])
|
|
->assertRedirect();
|
|
|
|
$this->assertDatabaseHas('meet_rooms', [
|
|
'title' => 'All hands',
|
|
'type' => 'town_hall',
|
|
]);
|
|
}
|
|
|
|
protected function createRoom(array $overrides = []): Room
|
|
{
|
|
return Room::create(array_merge([
|
|
'owner_ref' => $this->user->public_id,
|
|
'organization_id' => $this->organization->id,
|
|
'host_user_ref' => $this->user->public_id,
|
|
'title' => 'Test Meeting',
|
|
'type' => 'scheduled',
|
|
'status' => 'scheduled',
|
|
'scheduled_at' => now()->addDay(),
|
|
'timezone' => 'UTC',
|
|
'settings' => config('meet.default_settings'),
|
|
], $overrides));
|
|
}
|
|
}
|