feat(migration): create addresses and tenant_addresses tables feat(migration): add data migration for copying contact menu addresses test: add ContactAddressesTest for address and tenant address functionality
230 lines
9.9 KiB
PHP
230 lines
9.9 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Migrations;
|
|
|
|
use App\Domains\Core\Address\Models\Address;
|
|
use App\Domains\Core\Address\Models\TenantAddress;
|
|
use App\Domains\Core\Tenant\Models\Tenant;
|
|
use App\Domains\Core\Tenant\Services\TenantAddressService;
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
use Illuminate\Database\Migrations\Migration;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Tests\TestCase;
|
|
|
|
class ContactAddressesTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
Schema::create('tenants', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->string('codigo')->unique();
|
|
$table->string('address')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
Schema::create('tenants_menues', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->string('tenant_code');
|
|
$table->string('menu_code');
|
|
$table->json('static_content')->nullable();
|
|
$table->boolean('is_enabled')->default(true);
|
|
});
|
|
DB::table('tenants')->insert([
|
|
['codigo' => 'alpha', 'address' => 'Dirección anterior'],
|
|
['codigo' => 'beta', 'address' => null],
|
|
['codigo' => 'empty', 'address' => null],
|
|
]);
|
|
|
|
$this->schemaMigration()->up();
|
|
}
|
|
|
|
public function test_it_copies_all_contact_locations_without_changing_legacy_data_and_can_run_again(): void
|
|
{
|
|
$sharedAddress = [
|
|
'label' => 'Sede principal',
|
|
'address' => 'San Lorenzo 1543, Rosario',
|
|
'coordinates' => [-32.9431184, -60.6437991],
|
|
];
|
|
$this->contact('alpha', [
|
|
'rosario' => ['label' => 'Rosario', 'addresses' => [$sharedAddress]],
|
|
'santa_fe' => ['label' => 'Santa Fe', 'addresses' => [
|
|
['label' => 'Sucursal', 'address' => 'San Martín 100', 'coordinates' => [0, 0]],
|
|
['label' => 'Sin mapa', 'address' => 'San Martín 200'],
|
|
]],
|
|
], enabled: false);
|
|
$this->contact('beta', ['rosario' => ['addresses' => [$sharedAddress]]]);
|
|
$this->contact('empty', []);
|
|
DB::table('tenants_menues')->insert([
|
|
'tenant_code' => 'empty',
|
|
'menu_code' => 'help.faq',
|
|
'static_content' => json_encode(['locations' => [['addresses' => [$sharedAddress]]]]),
|
|
]);
|
|
$oldMenus = DB::table('tenants_menues')->orderBy('id')->get()->toArray();
|
|
|
|
$migration = $this->dataMigration();
|
|
$migration->up();
|
|
$migration->up();
|
|
|
|
$this->assertDatabaseCount('addresses', 4);
|
|
$this->assertDatabaseCount('tenant_addresses', 4);
|
|
$this->assertDatabaseHas('addresses', [
|
|
'label' => 'Sede principal',
|
|
'address_text' => 'San Lorenzo 1543, Rosario',
|
|
'latitude' => -32.9431184,
|
|
'longitude' => -60.6437991,
|
|
]);
|
|
$this->assertDatabaseHas('addresses', ['label' => 'Sucursal', 'latitude' => 0, 'longitude' => 0]);
|
|
$this->assertDatabaseHas('addresses', ['label' => 'Sin mapa', 'latitude' => null, 'longitude' => null]);
|
|
$this->assertEquals($oldMenus, DB::table('tenants_menues')->orderBy('id')->get()->toArray());
|
|
$this->assertDatabaseHas('tenants', ['codigo' => 'alpha', 'address' => 'Dirección anterior']);
|
|
|
|
$alpha = Tenant::query()->where('codigo', 'alpha')->firstOrFail();
|
|
$beta = Tenant::query()->where('codigo', 'beta')->firstOrFail();
|
|
$this->assertCount(3, $alpha->addresses);
|
|
$this->assertTrue($alpha->addresses->first()->pivot->is_main);
|
|
$this->assertInstanceOf(TenantAddress::class, $alpha->addresses->first()->pivot);
|
|
$this->assertSame('Sede principal', $alpha->mainAddress->label);
|
|
$this->assertSame('-32.9431184', $alpha->mainAddress->latitude);
|
|
$this->assertNotSame($alpha->mainAddress->id, $beta->mainAddress->id);
|
|
$this->assertSame('alpha', $alpha->mainAddress->tenants->sole()->codigo);
|
|
$this->assertNull(Tenant::query()->where('codigo', 'empty')->firstOrFail()->mainAddress);
|
|
$this->assertSame(2, DB::table('tenant_addresses')->where('is_main', true)->count());
|
|
|
|
$migration->down();
|
|
$this->assertDatabaseCount('addresses', 4);
|
|
$this->assertEquals($oldMenus, DB::table('tenants_menues')->orderBy('id')->get()->toArray());
|
|
}
|
|
|
|
public function test_a_tenant_can_have_many_secondary_addresses_but_only_one_main(): void
|
|
{
|
|
$tenant = Tenant::query()->where('codigo', 'alpha')->firstOrFail();
|
|
$addresses = $this->addresses(3);
|
|
$tenant->addresses()->attach($addresses[0], ['is_main' => true]);
|
|
$tenant->addresses()->attach([$addresses[1], $addresses[2]], ['is_main' => false]);
|
|
$this->assertCount(3, $tenant->addresses);
|
|
|
|
$this->expectException(QueryException::class);
|
|
$tenant->addresses()->updateExistingPivot($addresses[1], ['is_main' => true]);
|
|
}
|
|
|
|
public function test_it_prevents_duplicate_links(): void
|
|
{
|
|
$tenant = Tenant::query()->where('codigo', 'alpha')->firstOrFail();
|
|
$address = $this->addresses(1)[0];
|
|
$tenant->addresses()->attach($address);
|
|
|
|
$this->expectException(QueryException::class);
|
|
$tenant->addresses()->attach($address);
|
|
}
|
|
|
|
public function test_main_address_is_specific_to_each_tenant_and_can_be_switched(): void
|
|
{
|
|
$alpha = Tenant::query()->where('codigo', 'alpha')->firstOrFail();
|
|
$beta = Tenant::query()->where('codigo', 'beta')->firstOrFail();
|
|
$addresses = $this->addresses(2);
|
|
$alpha->addresses()->attach($addresses[0], ['is_main' => true]);
|
|
$alpha->addresses()->attach($addresses[1]);
|
|
$beta->addresses()->attach($addresses[0], ['is_main' => true]);
|
|
$this->assertSame($addresses[0], $alpha->mainAddress->id);
|
|
|
|
app(TenantAddressService::class)->setMainAddress($alpha, Address::findOrFail($addresses[1]));
|
|
|
|
$this->assertSame($addresses[1], $alpha->mainAddress->id);
|
|
$this->assertSame($addresses[0], $beta->mainAddress->id);
|
|
$this->assertSame(2, DB::table('tenant_addresses')->where('is_main', true)->count());
|
|
$loaded = Tenant::query()->with('mainAddress')->whereIn('codigo', ['alpha', 'beta'])->get()->keyBy('codigo');
|
|
$this->assertSame($addresses[1], $loaded['alpha']->mainAddress->id);
|
|
$this->assertSame($addresses[0], $loaded['beta']->mainAddress->id);
|
|
}
|
|
|
|
public function test_switching_to_an_unlinked_address_preserves_the_previous_main(): void
|
|
{
|
|
$tenant = Tenant::query()->where('codigo', 'alpha')->firstOrFail();
|
|
$addresses = $this->addresses(2);
|
|
$tenant->addresses()->attach($addresses[0], ['is_main' => true]);
|
|
|
|
try {
|
|
app(TenantAddressService::class)->setMainAddress($tenant, Address::findOrFail($addresses[1]));
|
|
$this->fail('An unlinked address must be rejected.');
|
|
} catch (ModelNotFoundException) {
|
|
$this->assertSame($addresses[0], $tenant->fresh()->mainAddress->id);
|
|
}
|
|
}
|
|
|
|
public function test_invalid_coordinates_roll_back_all_copies_and_preserve_source_data(): void
|
|
{
|
|
$this->contact('alpha', [['addresses' => [
|
|
['label' => 'Válida', 'address' => 'Primera 100', 'coordinates' => [-32, -60]],
|
|
['label' => 'Inválida', 'address' => 'Segunda 200', 'coordinates' => [-91, -60]],
|
|
]]]);
|
|
$oldContent = DB::table('tenants_menues')->value('static_content');
|
|
|
|
try {
|
|
$this->dataMigration()->up();
|
|
$this->fail('Invalid latitude must not be silently imported.');
|
|
} catch (\RuntimeException $exception) {
|
|
$this->assertStringContainsString('alpha', $exception->getMessage());
|
|
$this->assertDatabaseCount('addresses', 0);
|
|
$this->assertDatabaseCount('tenant_addresses', 0);
|
|
$this->assertSame($oldContent, DB::table('tenants_menues')->value('static_content'));
|
|
}
|
|
}
|
|
|
|
public function test_deleting_a_tenant_removes_links_but_keeps_shared_addresses(): void
|
|
{
|
|
$address = $this->addresses(1)[0];
|
|
foreach (['alpha', 'beta'] as $code) {
|
|
Tenant::query()->where('codigo', $code)->firstOrFail()->addresses()->attach($address, ['is_main' => true]);
|
|
}
|
|
Tenant::query()->where('codigo', 'alpha')->delete();
|
|
$this->assertDatabaseCount('tenant_addresses', 1);
|
|
$this->assertDatabaseCount('addresses', 1);
|
|
|
|
Address::findOrFail($address)->delete();
|
|
$this->assertDatabaseCount('tenant_addresses', 0);
|
|
}
|
|
|
|
public function test_schema_can_be_rolled_back_without_removing_legacy_tables(): void
|
|
{
|
|
$this->schemaMigration()->down();
|
|
$this->assertFalse(Schema::hasTable('addresses'));
|
|
$this->assertFalse(Schema::hasTable('tenant_addresses'));
|
|
$this->assertTrue(Schema::hasTable('tenants_menues'));
|
|
$this->assertTrue(Schema::hasColumn('tenants', 'address'));
|
|
}
|
|
|
|
private function contact(string $code, array $locations, bool $enabled = true): void
|
|
{
|
|
DB::table('tenants_menues')->insert([
|
|
'tenant_code' => $code,
|
|
'menu_code' => 'help.contact',
|
|
'static_content' => json_encode(['phone' => '+54 123', 'locations' => $locations], JSON_THROW_ON_ERROR),
|
|
'is_enabled' => $enabled,
|
|
]);
|
|
}
|
|
|
|
private function addresses(int $count): array
|
|
{
|
|
$ids = [];
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$ids[] = Address::create(['label' => "Sucursal {$i}", 'address_text' => "Calle {$i}"])->id;
|
|
}
|
|
|
|
return $ids;
|
|
}
|
|
|
|
private function schemaMigration(): Migration
|
|
{
|
|
return require database_path('migrations/2026_09_30_000000_create_addresses_and_tenant_addresses_tables.php');
|
|
}
|
|
|
|
private function dataMigration(): Migration
|
|
{
|
|
return require database_path('migrations/2026_09_30_000100_copy_contact_menu_addresses.php');
|
|
}
|
|
}
|