Initial Ladill Meet release.
Deploy Ladill Meet / deploy (push) Failing after 7s

Phases 0–18: core meetings, webinar, breakouts, team chat, live streaming, town hall, billing, and Ladill Mail calendar wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-30 23:35:29 +00:00
co-authored by Cursor
commit 965fb992e9
319 changed files with 30322 additions and 0 deletions
+343
View File
@@ -0,0 +1,343 @@
<?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_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($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));
}
}