From 35fd1277cb54d3a0a09ef847ec94d39b66deccd5 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Thu, 1 Oct 2026 11:37:45 -0300 Subject: [PATCH] feat(contact): implement contact management with endpoints for retrieving and updating contact details --- .../AdminApp/ContactController.php | 22 ++ .../AdminApp/UpdateContactRequest.php | 71 ++++++ .../Resources/AdminApp/ContactResource.php | 23 ++ .../Core/Tenant/Resources/TenantResource.php | 7 + .../Core/Tenant/Services/ContactService.php | 73 ++++++ .../Core/Tenant/documentacion/README.md | 9 +- app/Domains/Core/Tenant/routes/adminapp.php | 3 + .../Tenant/AdminAppContactControllerTest.php | 232 ++++++++++++++++++ .../Bootstrap/TenantContactResourceTest.php | 1 + 9 files changed, 440 insertions(+), 1 deletion(-) create mode 100644 app/Domains/Core/Tenant/Controllers/AdminApp/ContactController.php create mode 100644 app/Domains/Core/Tenant/Requests/AdminApp/UpdateContactRequest.php create mode 100644 app/Domains/Core/Tenant/Resources/AdminApp/ContactResource.php create mode 100644 app/Domains/Core/Tenant/Services/ContactService.php create mode 100644 tests/Feature/Tenant/AdminAppContactControllerTest.php diff --git a/app/Domains/Core/Tenant/Controllers/AdminApp/ContactController.php b/app/Domains/Core/Tenant/Controllers/AdminApp/ContactController.php new file mode 100644 index 00000000..ec63ea7e --- /dev/null +++ b/app/Domains/Core/Tenant/Controllers/AdminApp/ContactController.php @@ -0,0 +1,22 @@ +load($request->user()->tenant()->firstOrFail())); + } + + public function update(UpdateContactRequest $request, ContactService $service): ContactResource + { + return ContactResource::make($service->update($request->user()->tenant()->firstOrFail(), $request->validated())); + } +} diff --git a/app/Domains/Core/Tenant/Requests/AdminApp/UpdateContactRequest.php b/app/Domains/Core/Tenant/Requests/AdminApp/UpdateContactRequest.php new file mode 100644 index 00000000..fbf9bfe6 --- /dev/null +++ b/app/Domains/Core/Tenant/Requests/AdminApp/UpdateContactRequest.php @@ -0,0 +1,71 @@ +user()->tenant_codigo; + + return [ + 'social_media' => ['present', 'array', 'max:30'], + 'social_media.*' => ['array:code,url'], + 'social_media.*.code' => ['required', 'string', 'distinct', Rule::exists('social_media', 'code')], + 'social_media.*.url' => ['required', 'url:http,https', 'max:255'], + 'addresses' => ['present', 'array', 'list', 'max:50'], + 'addresses.*' => ['array:id,address_text,is_main'], + 'addresses.*.id' => ['nullable', 'integer', 'distinct', Rule::exists('tenant_addresses', 'address_id')->where('tenant_codigo', $tenantCode)], + 'addresses.*.address_text' => ['required', 'string', 'max:1000'], + 'addresses.*.is_main' => ['sometimes', 'boolean'], + 'phone_numbers' => ['present', 'array', 'list', 'max:50'], + 'phone_numbers.*' => ['array:id,country_code,mobile_prefix,area_code,local_number,is_main'], + 'phone_numbers.*.id' => ['nullable', 'integer', 'distinct', Rule::exists('tenant_phone_numbers', 'phone_number_id')->where('tenant_codigo', $tenantCode)], + 'phone_numbers.*.country_code' => ['present', 'nullable', 'regex:/^\+[0-9]{1,4}$/'], + 'phone_numbers.*.mobile_prefix' => ['present', 'nullable', 'regex:/^[0-9]{1,4}$/'], + 'phone_numbers.*.area_code' => ['present', 'nullable', 'regex:/^[0-9]{1,8}$/'], + 'phone_numbers.*.local_number' => ['required', 'string', 'max:50', 'regex:/^[0-9+() .-]+$/', 'regex:/[0-9]/'], + 'phone_numbers.*.is_main' => ['sometimes', 'boolean'], + ]; + } + + public function attributes(): array + { + return [ + 'social_media' => 'redes sociales', + 'social_media.*.code' => 'red social', + 'social_media.*.url' => 'URL de la red social', + 'addresses' => 'direcciones', + 'addresses.*.id' => 'dirección', + 'addresses.*.address_text' => 'dirección', + 'addresses.*.is_main' => 'dirección principal', + 'phone_numbers' => 'teléfonos', + 'phone_numbers.*.id' => 'teléfono', + 'phone_numbers.*.country_code' => 'código de país', + 'phone_numbers.*.mobile_prefix' => 'prefijo móvil', + 'phone_numbers.*.area_code' => 'código de área', + 'phone_numbers.*.local_number' => 'número de teléfono', + 'phone_numbers.*.is_main' => 'teléfono principal', + ]; + } + + public function messages(): array + { + return [ + 'required' => 'Completá :attribute.', + 'url' => 'La URL debe ser válida y comenzar con http:// o https://.', + 'regex' => 'El formato de :attribute no es válido.', + 'exists' => 'El valor de :attribute no está disponible para este comercio.', + 'distinct' => 'Hay un valor duplicado en :attribute.', + 'max.string' => 'El campo :attribute admite hasta :max caracteres.', + ]; + } +} diff --git a/app/Domains/Core/Tenant/Resources/AdminApp/ContactResource.php b/app/Domains/Core/Tenant/Resources/AdminApp/ContactResource.php new file mode 100644 index 00000000..de073520 --- /dev/null +++ b/app/Domains/Core/Tenant/Resources/AdminApp/ContactResource.php @@ -0,0 +1,23 @@ + $this->socialMedia->map(fn ($network) => [ + 'code' => $network->code, + 'url' => $network->pivot->url, + ])->values(), + 'addresses' => TenantAddressResource::collection($this->addresses), + 'phone_numbers' => PhoneNumberResource::collection($this->phoneNumbers), + ]; + } +} diff --git a/app/Domains/Core/Tenant/Resources/TenantResource.php b/app/Domains/Core/Tenant/Resources/TenantResource.php index fbd51c11..2985fbc8 100644 --- a/app/Domains/Core/Tenant/Resources/TenantResource.php +++ b/app/Domains/Core/Tenant/Resources/TenantResource.php @@ -105,6 +105,13 @@ class TenantResource extends JsonResource ]) ->values() ), + 'tenant_social_media' => $this->whenLoaded('socialMedia', fn () => $this->socialMedia + ->map(fn ($socialMedia) => [ + 'code' => $socialMedia->code, + 'icon' => $socialMedia->icon, + 'name' => $socialMedia->name, + 'url' => $socialMedia->pivot->url, + ])->values()), 'menues' => $this->whenLoaded( 'menues', fn () => $this->menuTree($this->menues) diff --git a/app/Domains/Core/Tenant/Services/ContactService.php b/app/Domains/Core/Tenant/Services/ContactService.php new file mode 100644 index 00000000..2842535d --- /dev/null +++ b/app/Domains/Core/Tenant/Services/ContactService.php @@ -0,0 +1,73 @@ +load([ + 'socialMedia', + 'addresses' => fn ($query) => $query->reorder()->orderByPivot('is_main', 'desc')->orderByPivot('id'), + 'phoneNumbers', + ]); + } + + public function update(Tenant $tenant, array $data): Tenant + { + return DB::transaction(function () use ($tenant, $data): Tenant { + // Serialize contact writes, including changes to the principal links. + Tenant::query()->whereKey($tenant->id)->lockForUpdate()->firstOrFail(); + $social = []; + foreach ($data['social_media'] as $index => $item) { + $social[$item['code']] = ['url' => $item['url'], 'orden' => $index]; + } + $tenant->socialMedia()->sync($social); + + foreach (['addresses', 'phone_numbers'] as $collection) { + $relation = $collection === 'addresses' ? $tenant->addresses() : $tenant->phoneNumbers(); + $existing = $relation->get()->keyBy('id'); + $links = []; + foreach (array_values($data[$collection]) as $index => $item) { + $id = $item['id'] ?? null; + if ($id !== null && ! $existing->has($id)) { + throw ValidationException::withMessages(["{$collection}.{$index}.id" => 'El contacto no pertenece al comercio.']); + } + $record = $id === null + ? ($collection === 'addresses' ? new Address : new PhoneNumber) + : $existing->get($id); + $attributes = collect($item)->except(['id', 'is_main'])->all(); + $record->fill($attributes); + if (! $record->exists) { + $record->label = $collection === 'addresses' ? 'Dirección' : 'Teléfono'; + } + if ($record->exists && $record->isDirty()) { + $shared = $record->tenants()->where('tenants.codigo', '!=', $tenant->codigo)->exists() + || ($record instanceof Address && $record->events()->exists()); + if ($shared) { + $record = $record->replicate(); + } + // An edited street address must not retain coordinates for the old location. + if ($record instanceof Address && $record->isDirty('address_text')) { + $record->latitude = null; + $record->longitude = null; + } + } + $record->save(); + $links[$record->id] = ['is_main' => $index === 0]; + } + // Clear the previous principal before sync to respect the unique index. + $relation->newPivotStatement()->where('tenant_codigo', $tenant->codigo)->update(['is_main' => false]); + $relation->sync($links); + } + + return $this->load($tenant); + }); + } +} diff --git a/app/Domains/Core/Tenant/documentacion/README.md b/app/Domains/Core/Tenant/documentacion/README.md index b8896209..ed9aa8d8 100644 --- a/app/Domains/Core/Tenant/documentacion/README.md +++ b/app/Domains/Core/Tenant/documentacion/README.md @@ -28,9 +28,16 @@ El par `(dominio, base_path)` es único. Un mismo dominio puede alojar el tenant - `GET /tenants`, `GET /tenants/{codigo}` y `DELETE /tenants/{codigo}`. - La creación y actualización general de tenants se administran mediante seeders o base de datos; no se exponen por API. - Bajo `/v1/adminapp/tenant/website-extras`, con autenticación y contexto de tenant: consulta general, detalle, actualización y activación/desactivación. +- `GET /v1/adminapp/tenant/contact` y `PUT /v1/adminapp/tenant/contact`, con `auth:sanctum` y `adminapp.tenant`: consulta y reemplazo completo del contacto del comercio autenticado. + +El PUT de contacto recibe `social_media` (`code`, `url`), `addresses` (`id` opcional para existentes, `address_text`) y `phone_numbers` (`id` opcional, `country_code`, `mobile_prefix`, `area_code`, `local_number`). Las tres colecciones son obligatorias; un array vacío elimina sus vínculos. El primer elemento enviado en cada lista de direcciones o teléfonos es siempre el principal: el backend calcula `is_main` por posición e ignora las marcas enviadas por clientes anteriores. GET y PUT devuelven el principal primero para conservarlo en el siguiente guardado. Los IDs existentes deben estar vinculados al comercio autenticado. Todos los cambios se realizan en una transacción, serializada por tenant, y la respuesta devuelve los registros persistidos y sus IDs. + +Se conservan etiquetas y coordenadas de direcciones sin modificar; al cambiar el texto se limpian las coordenadas para evitar mostrar el mapa de una ubicación anterior. Las ediciones de registros compartidos crean una copia para el comercio, preservando otros comercios y eventos. Las bajas desvinculan registros sin borrar datos compartidos. + +Contacto administra exclusivamente las redes del comercio. `TenantResource.tenant_social_media` siempre expone esas redes y alimenta Contacto y el footer públicos. `social_media` mantiene la selección histórica del evento activo; `event.social_media` sigue disponible para las vistas de evento. Guardar Contacto no modifica redes del evento. ## Dependencias y reglas Usa `Attachable` para logos y archivos. Es referenciado por casi todos los dominios para aislamiento. El `codigo` es clave de ruta y clave foránea heredada; no debe sustituirse por `id` sin una migración integral. -Los resources exponen `phone_numbers` (incluye `is_main`) y `main_phone_number`, con `id`, `label`, `number` y `tel_url`. Contacto muestra la colección y el footer usa el principal. La migración copia primero `tenants.phone` como principal y luego el teléfono de `help.contact`, si es distinto; si no existe teléfono del tenant, el del menú pasa a ser principal. Los duplicados por formato se comparan dentro de cada tenant sin inferir país ni código de área. Los campos anteriores se conservan como datos legacy, pero la tienda consume las relaciones nuevas. No hay endpoints de edición de teléfonos ni cambios del adminapp en esta etapa. +Los resources exponen `phone_numbers` (incluye `is_main`) y `main_phone_number`, con `id`, `label`, `number` y `tel_url`. Contacto muestra la colección y el footer usa el principal. La migración copia primero `tenants.phone` como principal y luego el teléfono de `help.contact`, si es distinto; si no existe teléfono del tenant, el del menú pasa a ser principal. Los duplicados por formato se comparan dentro de cada tenant sin inferir país ni código de área. Los campos anteriores se conservan como datos legacy, pero la tienda consume las relaciones nuevas y el adminapp las edita mediante el endpoint de contacto. diff --git a/app/Domains/Core/Tenant/routes/adminapp.php b/app/Domains/Core/Tenant/routes/adminapp.php index aa35510b..0e0d9512 100644 --- a/app/Domains/Core/Tenant/routes/adminapp.php +++ b/app/Domains/Core/Tenant/routes/adminapp.php @@ -1,6 +1,7 @@ middleware(['auth:sanctum', 'adminapp.tenant']) ->group(function (): void { + Route::get('contact', [ContactController::class, 'show']); + Route::put('contact', [ContactController::class, 'update']); Route::get('brand', [BrandController::class, 'show']); Route::put('brand', [BrandController::class, 'update']); Route::get('help-menus', [HelpMenuController::class, 'index']); diff --git a/tests/Feature/Tenant/AdminAppContactControllerTest.php b/tests/Feature/Tenant/AdminAppContactControllerTest.php new file mode 100644 index 00000000..25f55806 --- /dev/null +++ b/tests/Feature/Tenant/AdminAppContactControllerTest.php @@ -0,0 +1,232 @@ +id(); + $table->string('codigo')->unique(); + $table->string('nombre'); + $table->string('dominio'); + foreach (array_keys((new Tenant)->getAttributes()) as $column) { + $table->string($column)->nullable(); + } + $table->unsignedBigInteger('active_event_id')->nullable(); + $table->timestamps(); + }); + foreach ([ + '2026_07_23_000200_create_social_media_tables.php', + '2026_07_23_000300_add_orden_to_tenant_social_media_table.php', + '2026_09_30_000000_create_addresses_and_tenant_addresses_tables.php', + '2026_09_30_000400_create_phone_numbers_and_tenant_phone_numbers_tables.php', + '2026_09_30_000700_split_phone_number_components.php', + ] as $migration) { + (require database_path('migrations/'.$migration))->up(); + } + Schema::create('events', function (Blueprint $table): void { + $table->id(); + $table->string('tenant_code'); + $table->string('slug'); + $table->string('title'); + $table->unsignedBigInteger('address_id')->nullable(); + $table->timestamps(); + }); + Schema::create('event_social_media', function (Blueprint $table): void { + $table->id(); + $table->unsignedBigInteger('event_id'); + $table->string('social_media_code'); + $table->string('url'); + $table->unsignedInteger('orden'); + $table->timestamps(); + }); + $this->tenant = Tenant::create(['codigo' => 'contact-test', 'nombre' => 'Contact', 'dominio' => 'contact.test']); + SocialMedia::firstOrCreate(['code' => 'instagram'], ['name' => 'Instagram', 'icon' => 'instagram']); + } + + private function login(): void + { + Sanctum::actingAs(new User([ + 'rol_codigo' => RoleCode::AdminApp->value, + 'tenant_codigo' => $this->tenant->codigo, + ])); + } + + private function payload(): array + { + return [ + 'social_media' => [['code' => 'instagram', 'url' => 'https://instagram.com/contact']], + 'addresses' => [['address_text' => 'San Martín 123']], + 'phone_numbers' => [[ + 'country_code' => '+54', 'mobile_prefix' => null, 'area_code' => '341', + 'local_number' => '5551234', + ]], + ]; + } + + public function test_authentication_and_admin_role_are_required(): void + { + $this->getJson('/api/v1/adminapp/tenant/contact')->assertUnauthorized(); + $this->putJson('/api/v1/adminapp/tenant/contact', $this->payload())->assertUnauthorized(); + Sanctum::actingAs(new User(['tenant_codigo' => $this->tenant->codigo])); + $this->putJson('/api/v1/adminapp/tenant/contact', $this->payload())->assertForbidden(); + } + + public function test_contact_is_persisted_and_exposed_in_storefront_resource(): void + { + $this->login(); + $this->putJson('/api/v1/adminapp/tenant/contact', $this->payload())->assertOk() + ->assertJsonPath('data.phone_numbers.0.mobile_prefix', null) + ->assertJsonPath('data.addresses.0.is_main', true); + $this->getJson('/api/v1/adminapp/tenant/contact')->assertOk() + ->assertJsonPath('data.addresses.0.address_text', 'San Martín 123'); + $storefront = $this->storefrontData(); + $this->assertSame('https://instagram.com/contact', $storefront['tenant_social_media'][0]['url']); + $this->assertSame('San Martín 123', $storefront['main_address']->resolve()['address_text']); + $this->assertSame('tel:+543415551234', $storefront['main_phone_number']->resolve()['tel_url']); + } + + public function test_existing_contacts_can_be_updated_replaced_and_cleared(): void + { + $this->login(); + $first = $this->putJson('/api/v1/adminapp/tenant/contact', $this->payload())->assertOk()->json('data'); + $payload = $this->payload(); + $payload['addresses'] = [ + ['id' => $first['addresses'][0]['id'], 'address_text' => 'Updated', 'is_main' => false], + ['address_text' => 'New principal', 'is_main' => true], + ]; + $payload['phone_numbers'][0]['id'] = $first['phone_numbers'][0]['id']; + $payload['phone_numbers'][0]['local_number'] = '7771234'; + $payload['phone_numbers'][0]['is_main'] = false; + $payload['phone_numbers'][] = [...$this->payload()['phone_numbers'][0], 'local_number' => '8881234', 'is_main' => true]; + $updated = $this->putJson('/api/v1/adminapp/tenant/contact', $payload)->assertOk()->assertJsonCount(2, 'data.addresses')->json('data'); + // Position wins, even if a client sends conflicting is_main flags. + $this->assertSame('Updated', $this->tenant->mainAddress()->firstOrFail()->address_text); + $this->assertSame('7771234', $this->tenant->mainPhoneNumber()->firstOrFail()->local_number); + $payload['addresses'] = array_reverse($updated['addresses']); + $payload['addresses'] = array_map(fn ($item) => array_intersect_key($item, array_flip(['id', 'address_text'])), $payload['addresses']); + $payload['phone_numbers'] = array_reverse($payload['phone_numbers']); + $this->putJson('/api/v1/adminapp/tenant/contact', $payload)->assertOk() + ->assertJsonPath('data.addresses.0.address_text', 'New principal') + ->assertJsonPath('data.addresses.0.is_main', true) + ->assertJsonPath('data.addresses.1.is_main', false); + $this->getJson('/api/v1/adminapp/tenant/contact')->assertOk() + ->assertJsonPath('data.addresses.0.address_text', 'New principal'); + $this->assertSame('New principal', $this->tenant->mainAddress()->firstOrFail()->address_text); + $this->assertSame('8881234', $this->tenant->mainPhoneNumber()->firstOrFail()->local_number); + $this->putJson('/api/v1/adminapp/tenant/contact', [ + 'social_media' => [], 'addresses' => [], 'phone_numbers' => [], + ])->assertOk()->assertJsonCount(0, 'data.phone_numbers'); + $this->assertNull($this->tenant->mainAddress()->first()); + $this->assertNull($this->tenant->mainPhoneNumber()->first()); + } + + public function test_foreign_ids_and_invalid_payloads_do_not_change_contact(): void + { + $this->login(); + $this->putJson('/api/v1/adminapp/tenant/contact', $this->payload())->assertOk(); + $foreign = Address::create(['label' => 'Foreign', 'address_text' => 'Private']); + $payload = $this->payload(); + $payload['social_media'][0]['url'] = 'https://instagram.com/changed'; + $payload['addresses'][0]['id'] = $foreign->id; + $this->putJson('/api/v1/adminapp/tenant/contact', $payload)->assertUnprocessable()->assertJsonValidationErrors('addresses.0.id'); + $payload = $this->payload(); + $payload['phone_numbers'][0]['id'] = 999999; + $this->putJson('/api/v1/adminapp/tenant/contact', $payload)->assertUnprocessable()->assertJsonValidationErrors('phone_numbers.0.id'); + $payload = $this->payload(); + $payload['social_media'][0]['url'] = 'javascript:alert(1)'; + $payload['addresses'][] = $payload['addresses'][0]; + $this->putJson('/api/v1/adminapp/tenant/contact', $payload)->assertUnprocessable() + ->assertJsonValidationErrors(['social_media.0.url']); + $this->getJson('/api/v1/adminapp/tenant/contact')->assertJsonPath('data.social_media.0.url', 'https://instagram.com/contact'); + } + + public function test_service_rolls_back_all_changes_if_contact_membership_changes(): void + { + $this->login(); + $this->putJson('/api/v1/adminapp/tenant/contact', $this->payload())->assertOk(); + $payload = $this->payload(); + $payload['social_media'] = []; + $payload['phone_numbers'][0]['id'] = 999999; + try { + app(ContactService::class)->update($this->tenant, $payload); + $this->fail('An invalid contact ID must reject the whole transaction.'); + } catch (ValidationException) { + $this->assertSame(1, $this->tenant->socialMedia()->count()); + $this->assertSame(1, $this->tenant->addresses()->count()); + } + } + + public function test_shared_records_are_not_modified_for_other_tenants(): void + { + $this->login(); + $first = $this->putJson('/api/v1/adminapp/tenant/contact', $this->payload())->assertOk()->json('data'); + $other = Tenant::create(['codigo' => 'other-contact', 'nombre' => 'Other', 'dominio' => 'other-contact.test']); + $other->addresses()->attach($first['addresses'][0]['id'], ['is_main' => true]); + $other->phoneNumbers()->attach($first['phone_numbers'][0]['id'], ['is_main' => true]); + $payload = $this->payload(); + $payload['addresses'][0] = [...$payload['addresses'][0], 'id' => $first['addresses'][0]['id'], 'address_text' => 'Changed']; + $payload['phone_numbers'][0] = [...$payload['phone_numbers'][0], 'id' => $first['phone_numbers'][0]['id'], 'local_number' => '9991234']; + $this->putJson('/api/v1/adminapp/tenant/contact', $payload)->assertOk(); + $this->assertSame('San Martín 123', $other->addresses()->firstOrFail()->address_text); + $this->assertSame('5551234', $other->phoneNumbers()->firstOrFail()->local_number); + } + + public function test_contact_networks_are_independent_of_active_event_networks(): void + { + $this->login(); + $event = Event::create(['tenant_code' => $this->tenant->codigo, 'slug' => 'event', 'title' => 'Event']); + $event->socialMedia()->attach('instagram', ['url' => 'https://instagram.com/event', 'orden' => 0]); + $this->tenant->update(['active_event_id' => $event->id]); + $this->putJson('/api/v1/adminapp/tenant/contact', $this->payload())->assertOk(); + $this->getJson('/api/v1/adminapp/tenant/contact')->assertOk() + ->assertJsonPath('data.social_media.0.url', 'https://instagram.com/contact'); + $this->assertSame('https://instagram.com/event', $event->socialMedia()->firstOrFail()->pivot->url); + $tenant = $this->tenant->fresh()->load(['socialMedia', 'activeEvent.socialMedia']); + $this->assertSame('https://instagram.com/contact', $tenant->socialMedia->first()->pivot->url); + } + + private function storefrontData(): array + { + $tenant = $this->tenant->fresh()->load(['socialMedia', 'addresses', 'mainAddress', 'phoneNumbers', 'mainPhoneNumber']); + foreach (['favicon', 'headerLogo', 'footerLogo', 'headerBackgroundImage', 'footerBackgroundImage', 'storefrontWebsiteType'] as $relation) { + $tenant->setRelation($relation, null); + } + + return TenantResource::make($tenant)->resolve(request()); + } + + public function test_address_coordinates_are_preserved_until_the_address_changes(): void + { + $this->login(); + $address = Address::create(['label' => 'Sucursal', 'address_text' => 'Original', 'latitude' => -32.9, 'longitude' => -60.6]); + $this->tenant->addresses()->attach($address->id, ['is_main' => true]); + $payload = $this->payload(); + $payload['addresses'] = [['id' => $address->id, 'address_text' => 'Original', 'is_main' => true]]; + $this->putJson('/api/v1/adminapp/tenant/contact', $payload)->assertOk()->assertJsonPath('data.addresses.0.latitude', -32.9); + $payload['addresses'][0]['address_text'] = 'New address'; + $this->putJson('/api/v1/adminapp/tenant/contact', $payload)->assertOk()->assertJsonPath('data.addresses.0.latitude', null); + $this->assertSame('Sucursal', $address->fresh()->label); + } +} diff --git a/tests/Unit/Bootstrap/TenantContactResourceTest.php b/tests/Unit/Bootstrap/TenantContactResourceTest.php index d2d2b904..4c5d4315 100644 --- a/tests/Unit/Bootstrap/TenantContactResourceTest.php +++ b/tests/Unit/Bootstrap/TenantContactResourceTest.php @@ -42,6 +42,7 @@ class TenantContactResourceTest extends TestCase $this->assertArrayNotHasKey('static_content', $data['menues'][0]); $this->assertSame(['question' => 'Example'], $data['menues'][1]['static_content']); $this->assertSame('https://wa.me/222', $data['social_media'][0]['url']); + $this->assertSame('https://wa.me/111', $data['tenant_social_media'][0]['url']); $event->setRelation('socialMedia', collect()); $data = TenantResource::make($tenant)->resolve(request());