seed([AuthorizationSeeder::class, SocialMediaSeeder::class]); WebsiteType::query()->create([ 'codigo' => 'onticket', 'nombre' => 'OnTicket', ]); } public function test_authentication_is_required(): void { $this->getJson('/api/v1/adminapp/tenant/event')->assertUnauthorized(); $this->putJson('/api/v1/adminapp/tenant/event', $this->eventPayload())->assertUnauthorized(); } public function test_an_adminapp_user_can_create_the_active_event_and_contact_information(): void { $tenant = $this->createTenant('acme'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $response = $this->putJson('/api/v1/adminapp/tenant/event', $this->eventPayload()) ->assertOk() ->assertJsonPath('data.title', 'Festival Acme') ->assertJsonPath('data.location', 'Predio Ferial, Rosario') ->assertJsonPath('data.dates.0.date', '2026-10-09') ->assertJsonPath('data.dates.0.start_time', '09:00') ->assertJsonPath('data.dates.0.end_time', '18:30') ->assertJsonPath('data.contact.whatsapp_url', 'https://wa.me/5493415550101') ->assertJsonPath('data.contact.instagram_url', 'https://instagram.com/acme') ->assertJsonPath('data.contact.facebook_url', null); $this->assertSame($tenant->id, $response->json('data.id')); $this->assertDatabaseHas('tenants', [ 'id' => $tenant->id, 'event_title' => 'Festival Acme', 'event_location' => 'Predio Ferial, Rosario', ]); $this->assertDatabaseHas('event_dates', [ 'tenant_code' => $tenant->codigo, 'date' => '2026-10-09', 'time_start' => '09:00:00', 'time_end' => '18:30:00', ]); $this->assertDatabaseHas('tenant_social_media', [ 'tenant_code' => $tenant->codigo, 'social_media_code' => 'whatsapp', 'url' => 'https://wa.me/5493415550101', ]); } public function test_an_adminapp_user_can_read_only_its_tenant_active_event(): void { $tenant = $this->createTenant('acme'); $otherTenant = $this->createTenant('other'); $this->createActiveEvent($tenant, 'Acme Event'); $this->createActiveEvent($otherTenant, 'Other Event'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $this->getJson('/api/v1/adminapp/tenant/event') ->assertOk() ->assertJsonPath('data.title', 'Acme Event') ->assertJsonMissing(['title' => 'Other Event']); } public function test_reading_a_tenant_without_event_configuration_returns_empty_values(): void { $tenant = $this->createTenant('acme'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $this->getJson('/api/v1/adminapp/tenant/event') ->assertOk() ->assertJsonPath('data.title', null) ->assertJsonCount(0, 'data.dates'); } public function test_updating_reuses_the_active_event_and_synchronizes_dates_and_contact(): void { $tenant = $this->createTenant('acme'); $eventTenant = $this->createActiveEvent($tenant, 'Old Event'); $firstDate = $eventTenant->eventDates()->create([ 'date' => '2026-10-01', 'time_start' => '08:00', 'time_end' => '12:00', ]); $removedDate = $eventTenant->eventDates()->create([ 'date' => '2026-10-02', 'time_start' => '08:00', 'time_end' => '12:00', ]); $tenant->socialMedia()->attach('facebook', [ 'url' => 'https://facebook.com/old', 'orden' => 2, ]); $tenant->socialMedia()->attach('linkedin', [ 'url' => 'https://linkedin.com/company/acme', 'orden' => 3, ]); Sanctum::actingAs($this->createAdminAppUser($tenant)); $payload = $this->eventPayload(); $payload['dates'] = [[ 'date' => '2026-11-15', 'start_time' => '10:00', 'end_time' => '20:00', ]]; $this->putJson('/api/v1/adminapp/tenant/event', $payload) ->assertOk() ->assertJsonPath('data.id', $tenant->id) ->assertJsonPath('data.dates.0.id', $firstDate->id) ->assertJsonPath('data.contact.facebook_url', null); $this->assertDatabaseHas('event_dates', [ 'id' => $firstDate->id, 'date' => '2026-11-15', ]); $this->assertDatabaseMissing('event_dates', ['id' => $removedDate->id]); $this->assertDatabaseMissing('tenant_social_media', [ 'tenant_code' => $tenant->codigo, 'social_media_code' => 'facebook', ]); $this->assertDatabaseHas('tenant_social_media', [ 'tenant_code' => $tenant->codigo, 'social_media_code' => 'linkedin', 'url' => 'https://linkedin.com/company/acme', ]); } public function test_update_validates_event_dates_and_contact_urls(): void { $tenant = $this->createTenant('acme'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $this->putJson('/api/v1/adminapp/tenant/event', [ 'title' => '', 'location' => '', 'dates' => [ ['date' => '09/10/2026', 'start_time' => '9am', 'end_time' => '18:00'], ['date' => '09/10/2026', 'start_time' => '09:00', 'end_time' => '18:00'], ], 'contact' => [ 'whatsapp_url' => 'not-a-url', 'instagram_url' => null, 'facebook_url' => null, ], ]) ->assertUnprocessable() ->assertJsonValidationErrors([ 'title', 'location', 'dates.0.date', 'dates.0.start_time', 'dates.1.date', 'contact.whatsapp_url', ]); $this->assertNull($tenant->fresh()->event_title); } public function test_social_media_accepts_any_registered_code_and_rejects_unknown_codes(): void { $tenant = $this->createTenant('acme'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $payload = $this->eventPayload(); unset($payload['contact']); $payload['social_media'] = [[ 'code' => 'linkedin', 'url' => 'https://linkedin.com/company/acme', 'orden' => 5, ]]; $this->putJson('/api/v1/adminapp/tenant/event', $payload) ->assertOk() ->assertJsonPath('data.social_media.0.code', 'linkedin') ->assertJsonPath('data.social_media.0.url', 'https://linkedin.com/company/acme') ->assertJsonPath('data.social_media.0.orden', 5); $this->assertDatabaseHas('tenant_social_media', [ 'tenant_code' => $tenant->codigo, 'social_media_code' => 'linkedin', 'url' => 'https://linkedin.com/company/acme', 'orden' => 5, ]); $payload['social_media'][0]['code'] = 'unknown'; $this->putJson('/api/v1/adminapp/tenant/event', $payload) ->assertUnprocessable() ->assertJsonValidationErrors(['social_media.0.code']); } public function test_a_customer_cannot_manage_an_event(): void { Sanctum::actingAs(User::factory()->create([ 'rol_codigo' => RoleCode::User->value, 'tenant_codigo' => null, ])); $this->getJson('/api/v1/adminapp/tenant/event')->assertForbidden(); } /** @return array */ private function eventPayload(): array { return [ 'title' => 'Festival Acme', 'location' => 'Predio Ferial, Rosario', 'dates' => [[ 'date' => '2026-10-09', 'start_time' => '09:00', 'end_time' => '18:30', ]], 'contact' => [ 'whatsapp_url' => 'https://wa.me/5493415550101', 'instagram_url' => 'https://instagram.com/acme', 'facebook_url' => null, ], ]; } private function createTenant(string $code): Tenant { $headerLogo = $this->createAttachment("{$code}-header"); $footerLogo = $this->createAttachment("{$code}-footer"); return Tenant::query()->create([ 'codigo' => $code, 'nombre' => ucfirst($code), 'dominio' => "{$code}.test", 'website_type_code' => 'onticket', 'primary_color' => '#000000', 'secondary_color' => '#000000', 'danger_color' => '#000000', 'success_color' => '#000000', 'header_bg_color' => '#000000', 'footer_bg_color' => '#000000', 'header_logo_id' => $headerLogo->id, 'footer_logo_id' => $footerLogo->id, ]); } private function createAttachment(string $name): Attachment { return Attachment::query()->create([ 'path' => "test/{$name}.png", 'filename' => "{$name}.png", 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); } private function createAdminAppUser(Tenant $tenant): User { return User::factory()->create([ 'rol_codigo' => RoleCode::AdminApp->value, 'tenant_codigo' => $tenant->codigo, ]); } private function createActiveEvent(Tenant $tenant, string $name): Tenant { $tenant->update([ 'event_title' => $name, 'event_location' => 'Rosario', ]); return $tenant->fresh(); } }