id(); $table->string('codigo')->unique(); }); Schema::create('events', function (Blueprint $table): void { $table->id(); $table->string('tenant_code')->nullable(); $table->string('title')->nullable(); $table->string('subtitle')->nullable(); $table->timestamp('published_at')->nullable(); $table->foreignId('attachment_id')->nullable(); $table->string('location')->nullable(); $table->json('exact_location')->nullable(); $table->timestamps(); }); (require database_path('migrations/2026_09_30_000000_create_addresses_and_tenant_addresses_tables.php'))->up(); (require database_path('migrations/2026_09_30_000200_add_address_id_to_events.php'))->up(); } public function test_tenant_response_exposes_all_addresses_main_address_and_a_separate_event_address(): void { DB::table('tenants')->insert(['codigo' => 'acme']); $tenant = Tenant::where('codigo', 'acme')->firstOrFail(); $tenant->forceFill((new Tenant)->getAttributes()); $principal = Address::create(['label' => 'Principal', 'address_text' => 'Sede 123', 'latitude' => 0, 'longitude' => 0]); $secondary = Address::create(['label' => 'Sucursal', 'address_text' => 'Otra calle 456']); $venue = Address::create(['label' => 'Predio', 'address_text' => 'Evento 789', 'latitude' => -32.9431184, 'longitude' => -60.6437991]); $tenant->addresses()->attach($principal->id, ['is_main' => true]); $tenant->addresses()->attach($secondary->id); $tenant->load(['addresses', 'mainAddress']); foreach (['headerLogo', 'footerLogo', 'favicon', 'headerBackgroundImage', 'footerBackgroundImage', 'storefrontWebsiteType'] as $relation) { $tenant->setRelation($relation, null); } $event = $this->presentedEvent($venue, $tenant); $tenant->setRelation('activeEvent', $event); $data = (new TenantResource($tenant))->response()->getData(true)['data']; $this->assertCount(2, $data['addresses']); $this->assertTrue($data['addresses'][0]['is_main']); $this->assertFalse($data['addresses'][1]['is_main']); $this->assertSame(0, $data['main_address']['latitude']); $this->assertNull($data['addresses'][1]['latitude']); $this->assertSame($venue->id, $data['event']['address']['id']); $this->assertSame(-32.9431184, $data['event']['address']['latitude']); $this->assertSame('Evento 789', $data['event']['address']['address_text']); $this->assertSame('Anterior', $data['event']['location']); $this->assertArrayNotHasKey('is_main', $data['event']['address']); } public function test_public_and_admin_event_responses_use_the_same_canonical_address(): void { $address = Address::create(['label' => 'Predio', 'address_text' => 'Nueva 123', 'latitude' => 0, 'longitude' => -60.6437991]); $event = $this->presentedEvent($address, new Tenant); foreach ([EventResource::class, PublicEventResource::class] as $resource) { $data = (new $resource($event))->response()->getData(true)['data']; $this->assertSame('Nueva 123', $data['address']['address_text']); $this->assertSame(0, $data['address']['latitude']); $this->assertSame(-60.6437991, $data['address']['longitude']); $this->assertSame('Anterior', $data['location']); $this->assertSame(20, $data['exact_location']['longitude']); } $event->setRelation('address', null); $data = (new PublicEventResource($event))->response()->getData(true)['data']; $this->assertNull($data['address']); $this->assertSame('Anterior', $data['location']); $address->update(['latitude' => null, 'longitude' => null]); $event->setRelation('address', $address); $data = (new PublicEventResource($event))->response()->getData(true)['data']; $this->assertNull($data['address']['latitude']); $this->assertSame(20, $data['exact_location']['longitude']); } public function test_editing_event_location_preserves_canonical_coordinates_unless_explicitly_changed(): void { $address = Address::create(['label' => 'Predio', 'address_text' => 'Anterior', 'latitude' => 0, 'longitude' => -60]); $event = Event::create(['address_id' => $address->id, 'location' => 'Nueva', 'exact_location' => ['latitude' => 10, 'longitude' => 20]]); DB::transaction(fn () => app(EventAddressService::class)->syncLocation($event)); $this->assertSame('Nueva', $address->fresh()->address_text); $this->assertSame('0.0000000', $address->fresh()->latitude); $this->assertDatabaseCount('addresses', 1); $event->exact_location = null; DB::transaction(fn () => app(EventAddressService::class)->syncLocation($event, true)); $this->assertNull($address->fresh()->latitude); $this->assertNull($address->fresh()->longitude); } public function test_editing_a_shared_event_address_does_not_change_tenant_or_other_event_addresses(): void { DB::table('tenants')->insert(['codigo' => 'acme']); $tenant = Tenant::where('codigo', 'acme')->firstOrFail(); $address = Address::create(['label' => 'Compartida', 'address_text' => 'Original', 'latitude' => -32, 'longitude' => -60]); $tenant->addresses()->attach($address->id, ['is_main' => true]); $event = Event::create(['address_id' => $address->id, 'location' => 'Nueva ubicación']); $other = Event::create(['address_id' => $address->id, 'location' => 'Original']); DB::transaction(fn () => app(EventAddressService::class)->syncLocation($event)); $this->assertSame('Original', $address->fresh()->address_text); $this->assertSame($address->id, $other->fresh()->address_id); $this->assertSame($address->id, $tenant->mainAddress->id); $this->assertNotSame($address->id, $event->fresh()->address_id); $this->assertSame('Nueva ubicación', $event->fresh()->address->address_text); $this->assertSame('-32.0000000', $event->fresh()->address->latitude); } public function test_public_event_search_uses_its_address_and_loads_it_for_the_response(): void { DB::table('tenants')->insert(['codigo' => 'acme']); $tenant = Tenant::where('codigo', 'acme')->firstOrFail(); $address = Address::create(['label' => 'Predio', 'address_text' => 'Dirección nueva']); $event = Event::create([ 'tenant_code' => 'acme', 'title' => 'Festival', 'location' => 'Dirección anterior', 'address_id' => $address->id, 'published_at' => now()->subMinute(), ]); $service = app(PublicEventService::class); $page = $service->list($tenant, 'nueva', 1); $this->assertSame($event->id, $page->items()[0]->id); $this->assertTrue($page->items()[0]->relationLoaded('address')); $this->assertSame(0, $service->list($tenant, 'anterior', 1)->total()); } public function test_editing_an_unlinked_event_creates_its_address(): void { $event = Event::create(['location' => 'Predio 123', 'exact_location' => ['latitude' => -32, 'longitude' => -60]]); DB::transaction(fn () => app(EventAddressService::class)->syncLocation($event)); $this->assertSame('Predio 123', $event->fresh()->address->address_text); $this->assertSame('-60.0000000', $event->fresh()->address->longitude); $this->assertDatabaseCount('tenant_addresses', 0); } private function presentedEvent(?Address $address, Tenant $tenant): Event { $event = new Event(['location' => 'Anterior', 'exact_location' => ['latitude' => 10, 'longitude' => 20]]); $event->setRelation('address', $address)->setRelation('tenant', $tenant) ->setRelation('socialMedia', collect())->setRelation('dates', collect())->setRelation('attachment', null); return $event; } }