'https://mail.ladill.com/api/service/v1', 'mail_calendar.api_key' => 'test-events-mail-key', 'identity.api_url' => 'https://ladill.com/api', 'identity.api_key' => 'test-identity-key', ]); Http::fake([ 'https://ladill.com/api/identity/mailbox-link*' => Http::response(['data' => []]), 'https://mail.ladill.com/*' => Http::response(['data' => ['id' => 55]], 201), ]); } public function test_in_person_event_syncs_to_mail_calendar(): void { $owner = User::factory()->create(['public_id' => 'usr-cal-1', 'email' => 'host@example.com']); $event = $this->makeEvent($owner, [ 'format' => 'in_person', 'name' => 'Town hall', 'starts_at' => now()->addWeek()->toDateString(), 'ends_at' => now()->addWeek()->addHours(3)->toDateString(), ]); app(EventMailCalendarSyncService::class)->sync($event, $owner); Http::assertSent(function ($request) use ($event) { return str_contains($request->url(), '/calendar/events') && $request['mailbox_email'] === 'host@example.com' && $request['external_ref'] === 'events:'.$event->id && $request['title'] === 'Town hall'; }); $this->assertSame( ['events:'.$event->id => 55], data_get($event->fresh()->payload, 'calendar_sync.items'), ); } public function test_virtual_session_syncs_one_entry_with_meet_join_url(): void { $owner = User::factory()->create(['public_id' => 'usr-cal-3', 'email' => 'host@example.com']); $event = $this->makeEvent($owner, [ 'format' => 'virtual', 'name' => 'Product launch', 'starts_at' => now()->addWeek()->toDateString(), 'virtual_sessions' => [[ 'id' => 'vs-main', 'title' => 'Main stage', 'scheduled_at' => now()->addWeek()->toIso8601String(), 'duration_minutes' => 90, 'join_url' => 'https://meet.ladill.com/join/demo', ]], ]); app(EventMailCalendarSyncService::class)->sync($event, $owner); Http::assertSent(function ($request) use ($event) { return str_contains($request->url(), '/calendar/events') && $request['external_ref'] === 'events:'.$event->id.':session:vs-main' && $request['join_url'] === 'https://meet.ladill.com/join/demo'; }); $this->assertSame(1, collect(Http::recorded())->filter( fn (array $pair) => str_contains($pair[0]->url(), '/calendar/events'), )->count()); } public function test_virtual_session_without_id_still_syncs_to_mail_calendar(): void { $owner = User::factory()->create(['public_id' => 'usr-cal-4', 'email' => 'host@example.com']); $event = $this->makeEvent($owner, [ 'format' => 'virtual', 'name' => 'NextGen Summit 2026', 'starts_at' => '2026-08-26', 'ends_at' => '2026-08-27', 'virtual_sessions' => [[ 'id' => null, 'title' => 'NextGen Summit', 'scheduled_at' => '2026-08-26', 'duration_minutes' => 180, ]], ]); app(EventMailCalendarSyncService::class)->sync($event, $owner); Http::assertSent(function ($request) use ($event) { return str_contains($request->url(), '/calendar/events') && str_starts_with((string) $request['external_ref'], 'events:'.$event->id.':session:vs-') && $request['title'] === 'NextGen Summit'; }); $this->assertNotEmpty(data_get($event->fresh()->payload, 'calendar_sync.items')); } public function test_uses_linked_mailbox_when_available(): void { Http::fake([ 'https://mail.ladill.com/*' => Http::response(['data' => ['id' => 77]], 201), ]); $this->mock(\App\Services\Identity\IdentityClient::class, function ($mock) { $mock->shouldReceive('mailboxLinkStatus') ->once() ->andReturn(['linked_mailbox' => 'owner@customdomain.com']); }); $owner = User::factory()->create(['public_id' => 'usr-cal-2', 'email' => 'sso@ladill.com']); $event = $this->makeEvent($owner, [ 'format' => 'in_person', 'name' => 'Gala', 'starts_at' => now()->addWeek()->toDateString(), ]); app(EventMailCalendarSyncService::class)->sync($event, $owner); Http::assertSent(function ($request) { return str_contains($request->url(), '/calendar/events') && ($request['mailbox_email'] ?? '') === 'owner@customdomain.com'; }); } /** @param array $content */ private function makeEvent(User $owner, array $content): QrCode { return QrCode::create([ 'user_id' => $owner->id, 'short_code' => 'evt-cal-'.uniqid(), 'type' => QrCode::TYPE_EVENT, 'label' => (string) ($content['name'] ?? 'Event'), 'payload' => ['content' => $content, 'style' => []], 'is_active' => true, ]); } }