Merge branch 'tenant/smep' into homo

This commit is contained in:
2026-09-28 19:27:39 +00:00
541 changed files with 6294 additions and 9 deletions

View File

@@ -72,6 +72,29 @@ class CategoryDetailTest extends TestCase
->assertJsonCount(0, 'data');
}
public function test_it_includes_products_from_all_descendant_categories(): void
{
$tenant = $this->createTenant('category-descendants');
$parent = $this->createCategory($tenant, 'Indumentaria');
$child = $this->createCategory($tenant, 'Remeras', $parent);
$grandchild = $this->createCategory($tenant, 'Manga corta', $child);
$sibling = $this->createCategory($tenant, 'Pantalones');
$parentItem = $this->createCatalogItem($tenant, $parent, 'Producto padre');
$childItem = $this->createCatalogItem($tenant, $child, 'Producto hijo');
$grandchildItem = $this->createCatalogItem($tenant, $grandchild, 'Producto nieto');
$this->createCatalogItem($tenant, $sibling, 'Producto ajeno');
$this->getJson("/api/tenants/{$tenant->codigo}/categories/{$parent->id}")
->assertOk()
->assertJsonPath('meta.total', 3)
->assertJsonCount(3, 'data')
->assertJsonPath('data.0.id', $childItem->id)
->assertJsonPath('data.1.id', $grandchildItem->id)
->assertJsonPath('data.2.id', $parentItem->id)
->assertJsonMissing(['nombre' => 'Producto ajeno']);
}
public function test_it_rejects_categories_from_another_tenant(): void
{
$tenant = $this->createTenant('category-owner');
@@ -107,10 +130,14 @@ class CategoryDetailTest extends TestCase
]);
}
private function createCategory(Tenant $tenant, string $name): Category
{
private function createCategory(
Tenant $tenant,
string $name,
?Category $parent = null,
): Category {
return Category::query()->create([
'tenant_code' => $tenant->codigo,
'categoria_id' => $parent?->id,
'nombre' => $name,
]);
}

View File

@@ -0,0 +1,197 @@
<?php
namespace Tests\Feature\Migrations;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class ProvisionMutualSmepTenantTest extends TestCase
{
public function test_it_provisions_the_mutual_smep_tenant_once_with_its_branding(): void
{
Storage::fake('s3');
$this->createSchema();
DB::table('admin_website_types')->insert(['codigo' => 'shopit']);
DB::table('storefront_website_types')->insert(['codigo' => 'shopit']);
DB::table('menues')->insert([
['code' => 'product.detail'],
['code' => 'help.faq'],
['code' => 'help.contact'],
['code' => 'account.tickets'],
['code' => 'event.index'],
]);
$migration = require database_path('migrations/2026_09_24_000000_provision_mutual_smep_tenant.php');
$migration->up();
$migration->up();
$this->assertDatabaseCount('tenants', 1);
$this->assertDatabaseHas('clients', [
'code' => 'mutual_smep',
'name' => 'Mutual SMEP',
]);
$this->assertDatabaseHas('tenants', [
'codigo' => 'mutual_smep',
'nombre' => 'Mutual SMEP',
'dominio' => 'mutual-smep.localhost',
'site_title' => 'Tienda Mutual SMEP',
'primary_color' => '#4A7FF5',
'secondary_color' => '#0051A4',
'header_bg_color' => '#FFFFFF',
'footer_bg_color' => '#0051A4',
'admin_website_type_code' => 'shopit',
'storefront_website_type_code' => 'shopit',
'display_categories' => true,
'display_seach_bar' => true,
'display_cart' => true,
'cart_editing_policy' => 'quantity_and_remove',
'checkout_editing_policy' => 'disabled',
]);
$this->assertSame([
'mutual_smep_favicon.png',
'mutual_smep_footer.png',
'mutual_smep_header.png',
], DB::table('attachments')->orderBy('filename')->pluck('filename')->all());
DB::table('attachments')->orderBy('id')->each(
fn (object $attachment) => Storage::disk('s3')->assertExists($attachment->path)
);
$this->assertSame(
['instagram', 'facebook', 'whatsapp'],
DB::table('tenant_social_media')
->where('tenant_code', 'mutual_smep')
->orderBy('orden')
->pluck('social_media_code')
->all(),
);
$this->assertDatabaseHas('tenants_menues', [
'tenant_code' => 'mutual_smep',
'menu_code' => 'product.detail',
]);
$this->assertDatabaseMissing('tenants_menues', [
'tenant_code' => 'mutual_smep',
'menu_code' => 'account.tickets',
]);
$this->assertDatabaseMissing('tenants_menues', [
'tenant_code' => 'mutual_smep',
'menu_code' => 'event.index',
]);
$contact = json_decode(
DB::table('tenants_menues')
->where('tenant_code', 'mutual_smep')
->where('menu_code', 'help.contact')
->value('static_content'),
true,
flags: JSON_THROW_ON_ERROR,
);
$this->assertSame('https://wa.me/5493412474530', $contact['whatsapp']['whatsapp_url']);
$this->assertSame('San Lorenzo 1543, Rosario, Santa Fe', $contact['locations']['rosario']['addresses'][0]['address']);
$storedPaths = DB::table('attachments')->pluck('path')->all();
$migration->down();
$this->assertDatabaseMissing('tenants', ['codigo' => 'mutual_smep']);
$this->assertDatabaseMissing('clients', ['code' => 'mutual_smep']);
$this->assertDatabaseCount('attachments', 0);
$this->assertDatabaseCount('tenant_social_media', 0);
$this->assertDatabaseCount('tenants_menues', 0);
foreach ($storedPaths as $storedPath) {
Storage::disk('s3')->assertMissing($storedPath);
}
}
private function createSchema(): void
{
Schema::create('clients', function (Blueprint $table): void {
$table->id();
$table->string('code')->unique();
$table->string('name');
$table->timestamps();
});
Schema::create('attachments', function (Blueprint $table): void {
$table->id();
$table->uuid('key');
$table->string('path');
$table->string('filename');
$table->string('type');
$table->string('mime_type');
$table->string('extension');
$table->unsignedBigInteger('size');
$table->timestamps();
});
Schema::create('admin_website_types', function (Blueprint $table): void {
$table->id();
$table->string('codigo')->unique();
});
Schema::create('storefront_website_types', function (Blueprint $table): void {
$table->id();
$table->string('codigo')->unique();
});
Schema::create('tenants', function (Blueprint $table): void {
$table->id();
$table->foreignId('client_id');
$table->string('codigo')->unique();
$table->string('nombre');
$table->string('timezone');
$table->string('dominio');
$table->string('base_path');
$table->string('site_title');
$table->string('address');
$table->string('phone');
$table->string('primary_color');
$table->string('secondary_color');
$table->string('danger_color');
$table->string('success_color');
$table->string('header_bg_color');
$table->string('footer_bg_color');
$table->foreignId('header_logo_id');
$table->foreignId('footer_logo_id');
$table->foreignId('favicon_id');
$table->string('admin_website_type_code');
$table->string('storefront_website_type_code');
$table->boolean('display_categories');
$table->boolean('display_seach_bar');
$table->boolean('display_cart');
$table->string('cart_editing_policy');
$table->string('checkout_editing_policy');
$table->timestamps();
});
Schema::create('social_media', function (Blueprint $table): void {
$table->id();
$table->string('code')->unique();
$table->string('icon');
$table->string('name');
$table->timestamps();
});
Schema::create('tenant_social_media', function (Blueprint $table): void {
$table->id();
$table->string('tenant_code');
$table->string('social_media_code');
$table->string('url');
$table->unsignedInteger('orden');
$table->timestamps();
$table->unique(['tenant_code', 'social_media_code']);
});
Schema::create('menues', function (Blueprint $table): void {
$table->id();
$table->string('code')->unique();
});
Schema::create('tenants_menues', function (Blueprint $table): void {
$table->id();
$table->string('tenant_code');
$table->string('menu_code');
$table->json('static_content')->nullable();
$table->timestamps();
$table->unique(['tenant_code', 'menu_code']);
});
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace Tests\Feature\Migrations;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class SeedMutualSmepCarouselTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Storage::fake('s3');
$this->createSchema();
DB::table('tenants')->insert([
'codigo' => 'mutual_smep',
'storefront_website_type_code' => 'shopit',
]);
DB::table('storefront_website_type_extras')->insert([
'storefront_website_type_code' => 'shopit',
'codigo' => 'carousel',
]);
}
protected function tearDown(): void
{
foreach (['websites_extras', 'attachments', 'storefront_website_type_extras', 'tenants'] as $table) {
Schema::dropIfExists($table);
}
parent::tearDown();
}
public function test_it_loads_and_rolls_back_the_mutual_smep_carousel(): void
{
$migration = require database_path(
'migrations/2026_09_24_040000_seed_mutual_smep_carousel.php'
);
$migration->up();
$migration->up();
$this->assertDatabaseCount('attachments', 4);
$this->assertDatabaseCount('websites_extras', 1);
$attachments = DB::table('attachments')->orderBy('filename')->get();
$this->assertSame([
'01-tecnologia.webp',
'02-hogar.webp',
'03-vida-activa.webp',
'04-descanso.webp',
], $attachments->pluck('filename')->all());
$this->assertSame(
$attachments->pluck('id')->all(),
json_decode(
DB::table('websites_extras')->value('config'),
true,
flags: JSON_THROW_ON_ERROR,
),
);
foreach ($attachments as $attachment) {
$this->assertSame('image/webp', $attachment->mime_type);
$this->assertStringStartsWith(
'tenants/mutual_smep/extras/carousel-v1/',
$attachment->path,
);
Storage::disk('s3')->assertExists($attachment->path);
}
$storedPaths = $attachments->pluck('path')->all();
$migration->down();
$this->assertDatabaseCount('websites_extras', 0);
$this->assertDatabaseCount('attachments', 0);
foreach ($storedPaths as $storedPath) {
Storage::disk('s3')->assertMissing($storedPath);
}
}
private function createSchema(): void
{
Schema::create('tenants', function (Blueprint $table): void {
$table->string('codigo')->primary();
$table->string('storefront_website_type_code');
});
Schema::create('storefront_website_type_extras', function (Blueprint $table): void {
$table->id();
$table->string('storefront_website_type_code');
$table->string('codigo');
});
Schema::create('attachments', function (Blueprint $table): void {
$table->id();
$table->uuid('key')->unique();
$table->string('path');
$table->string('filename');
$table->string('type');
$table->string('mime_type');
$table->string('extension')->nullable();
$table->unsignedBigInteger('size')->default(0);
$table->timestamps();
});
Schema::create('websites_extras', function (Blueprint $table): void {
$table->id();
$table->string('website_code');
$table->unsignedBigInteger('website_type_extra_id');
$table->json('config');
$table->boolean('is_enabled')->default(true);
$table->timestamps();
$table->unique(['website_code', 'website_type_extra_id']);
});
}
}

View File

@@ -0,0 +1,375 @@
<?php
namespace Tests\Feature\Migrations;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class SeedMutualSmepCatalogTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Storage::fake('s3');
$this->createSchema();
DB::table('tenants')->insert(['codigo' => 'mutual_smep']);
$this->seedCategoriesAndAttributes();
}
protected function tearDown(): void
{
foreach ([
'featured_items',
'featured_groups',
'catalog_items_attachments',
'attachments',
'variant_values',
'item_attributes',
'variantes',
'catalog_items',
'inventories',
'brands',
'attribute',
'categorias',
'tenants',
] as $table) {
Schema::dropIfExists($table);
}
parent::tearDown();
}
public function test_it_loads_and_rolls_back_the_complete_catalog(): void
{
$migration = require database_path(
'migrations/2026_09_24_030000_seed_mutual_smep_catalog.php'
);
$migration->up();
$this->assertSame(142, DB::table('catalog_items')->count());
$this->assertSame(150, DB::table('inventories')->count());
$this->assertSame(15, DB::table('variantes')->count());
$this->assertSame(8, DB::table('item_attributes')->count());
$this->assertSame(17, DB::table('variant_values')->count());
$this->assertSame(511, DB::table('attachments')->count());
$this->assertSame(511, DB::table('catalog_items_attachments')->count());
$this->assertSame(
511,
count(Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v2')),
);
$this->assertDatabaseCount('featured_groups', 7);
$this->assertSame([
'Productos destacados',
'Dormitorio y Blanco',
'Electrodomésticos',
'Climatización',
'Tecnología',
'Bicicletas y Aire Libre',
'Bazar',
], DB::table('featured_groups')->orderBy('group_order')->pluck('group_name')->all());
DB::table('featured_groups')->orderBy('group_order')->each(function (object $group): void {
$this->assertSame('manual', $group->source_type);
$this->assertSame('carousel', $group->group_layout);
$this->assertSame('column_with_image', $group->product_layout);
if ($group->code === 'productos-destacados-smep') {
$this->assertNull($group->category_id);
} else {
$this->assertNotNull($group->category_id);
$this->assertDatabaseHas('categorias', [
'id' => $group->category_id,
'tenant_code' => 'mutual_smep',
'categoria_id' => null,
'nombre' => $group->group_name,
]);
}
$this->assertGreaterThan(
0,
DB::table('featured_items')->where('featured_group_id', $group->id)->count(),
);
});
$highlightedGroupId = DB::table('featured_groups')
->where('code', 'productos-destacados-smep')
->value('id');
$highlightedSlugs = DB::table('featured_items')
->join('catalog_items', 'catalog_items.id', '=', 'featured_items.catalog_item_id')
->where('featured_items.featured_group_id', $highlightedGroupId)
->orderBy('featured_items.order')
->pluck('catalog_items.slug')
->all();
$variantProductSlugs = DB::table('catalog_items')
->whereExists(fn ($query) => $query
->selectRaw('1')
->from('variantes')
->whereColumn('variantes.catalog_item_id', 'catalog_items.id'))
->pluck('slug')
->all();
$this->assertCount(13, $highlightedSlugs);
$this->assertSame([], array_values(array_diff($variantProductSlugs, $highlightedSlugs)));
$this->assertSame(46, DB::table('featured_items')->count());
DB::table('featured_groups')
->where('code', '!=', 'productos-destacados-smep')
->orderBy('id')
->each(function (object $group): void {
$this->assertLessThanOrEqual(
6,
DB::table('featured_items')->where('featured_group_id', $group->id)->count(),
);
});
$this->assertDatabaseHas('catalog_items', [
'tenant_code' => 'mutual_smep',
'slug' => 'motorola-g15',
'precio' => 330000,
]);
$this->assertSame(10, DB::table('inventories')->min('real_stock'));
$this->assertSame(10, DB::table('inventories')->max('real_stock'));
$migration->down();
foreach ([
'featured_items',
'featured_groups',
'catalog_items_attachments',
'attachments',
'variant_values',
'item_attributes',
'variantes',
'catalog_items',
'inventories',
'brands',
] as $table) {
$this->assertSame(0, DB::table($table)->count(), $table);
}
$this->assertSame([], Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v2'));
}
public function test_it_limits_existing_category_groups_and_restores_them_on_rollback(): void
{
$catalogMigration = require database_path(
'migrations/2026_09_24_030000_seed_mutual_smep_catalog.php'
);
$limitMigration = require database_path(
'migrations/2026_09_24_050000_limit_mutual_smep_category_featured_groups.php'
);
$catalogMigration->up();
$limitMigration->down();
$this->assertSame(155, DB::table('featured_items')->count());
$limitMigration->up();
$this->assertSame(46, DB::table('featured_items')->count());
$categoryGroupCounts = DB::table('featured_groups')
->leftJoin('featured_items', 'featured_items.featured_group_id', '=', 'featured_groups.id')
->where('featured_groups.code', '!=', 'productos-destacados-smep')
->groupBy('featured_groups.id', 'featured_groups.group_name', 'featured_groups.group_order')
->orderBy('featured_groups.group_order')
->selectRaw('featured_groups.group_name, COUNT(featured_items.id) AS item_count')
->pluck('item_count', 'featured_groups.group_name')
->map(fn ($count): int => (int) $count)
->all();
$this->assertSame([
'Dormitorio y Blanco' => 6,
'Electrodomésticos' => 6,
'Climatización' => 6,
'Tecnología' => 6,
'Bicicletas y Aire Libre' => 6,
'Bazar' => 3,
], $categoryGroupCounts);
$limitMigration->down();
$this->assertSame(155, DB::table('featured_items')->count());
$catalogMigration->down();
}
public function test_it_upgrades_legacy_catalog_images_and_restores_them_on_rollback(): void
{
$catalogMigration = require database_path(
'migrations/2026_09_24_030000_seed_mutual_smep_catalog.php'
);
$imageMigration = require database_path(
'migrations/2026_09_24_060000_optimize_mutual_smep_catalog_images.php'
);
$catalogMigration->up();
DB::table('attachments')->orderBy('id')->each(function (object $attachment): void {
$optimizedPath = (string) $attachment->path;
$legacyPath = str_replace('catalog-v2/', 'catalog-v1/', $optimizedPath);
$contents = Storage::disk('s3')->get($optimizedPath);
Storage::disk('s3')->put($legacyPath, $contents.'legacy');
Storage::disk('s3')->delete($optimizedPath);
DB::table('attachments')->where('id', $attachment->id)->update([
'path' => $legacyPath,
'size' => strlen($contents.'legacy'),
]);
});
$imageMigration->up();
$this->assertSame(511, count(Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v2')));
$this->assertSame(0, DB::table('attachments')->where('path', 'like', '%catalog-v1/%')->count());
$this->assertSame(511, DB::table('attachments')->where('path', 'like', '%catalog-v2/%')->count());
$imageMigration->down();
$this->assertSame([], Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v2'));
$this->assertSame(511, DB::table('attachments')->where('path', 'like', '%catalog-v1/%')->count());
$catalogMigration->down();
$this->assertSame([], Storage::disk('s3')->allFiles('tenants/mutual_smep/catalog-v1'));
}
private function seedCategoriesAndAttributes(): void
{
$manifest = json_decode(
(string) file_get_contents(database_path('data/mutual_smep_catalog_v1.json')),
true,
flags: JSON_THROW_ON_ERROR,
);
$parents = [];
foreach ($manifest['items'] as $item) {
$parentName = $item['parent_category'];
$parentId = $parents[$parentName] ??= DB::table('categorias')->insertGetId([
'tenant_code' => 'mutual_smep',
'categoria_id' => null,
'nombre' => $parentName,
]);
DB::table('categorias')->insertOrIgnore([
'tenant_code' => 'mutual_smep',
'categoria_id' => $parentId,
'nombre' => $item['category'],
]);
}
foreach (['color', 'medida_cama', 'almacenamiento', 'memoria_ram', 'rodado'] as $code) {
DB::table('attribute')->insert([
'tenant_codigo' => 'mutual_smep',
'codigo' => $code,
'nombre' => $code,
]);
}
}
private function createSchema(): void
{
Schema::create('tenants', fn (Blueprint $table) => $table->string('codigo')->primary());
Schema::create('categorias', function (Blueprint $table): void {
$table->id();
$table->string('tenant_code');
$table->unsignedBigInteger('categoria_id')->nullable();
$table->string('nombre');
$table->unique(['tenant_code', 'categoria_id', 'nombre']);
});
Schema::create('attribute', function (Blueprint $table): void {
$table->id();
$table->string('tenant_codigo');
$table->string('codigo');
$table->string('nombre');
});
Schema::create('brands', function (Blueprint $table): void {
$table->id();
$table->string('tenant_codigo');
$table->string('nombre');
$table->text('descripcion')->nullable();
$table->timestamps();
});
Schema::create('inventories', function (Blueprint $table): void {
$table->id();
$table->unsignedBigInteger('sold_units')->default(0);
$table->unsignedBigInteger('refunded_units')->default(0);
$table->unsignedInteger('reserved_stock')->default(0);
$table->unsignedInteger('real_stock')->default(0);
});
Schema::create('catalog_items', function (Blueprint $table): void {
$table->id();
$table->string('tenant_code');
$table->unsignedBigInteger('category_id')->nullable();
$table->unsignedBigInteger('brand_id')->nullable();
$table->unsignedBigInteger('inventory_id')->nullable();
$table->string('type');
$table->string('slug');
$table->string('nombre');
$table->unsignedInteger('group_order')->default(0);
$table->text('descripcion')->nullable();
$table->decimal('precio', 10, 2);
$table->string('inventory_policy');
$table->string('inventory_subject');
$table->boolean('has_tickets')->default(false);
$table->unique(['tenant_code', 'slug']);
});
Schema::create('variantes', function (Blueprint $table): void {
$table->id();
$table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete();
$table->unsignedBigInteger('inventory_id');
$table->text('descripcion')->nullable();
$table->decimal('precio', 10, 2)->nullable();
});
Schema::create('item_attributes', function (Blueprint $table): void {
$table->id();
$table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete();
$table->unsignedBigInteger('attribute_id');
$table->boolean('allow_multi_select')->default(false);
$table->unsignedInteger('sort_order')->default(0);
$table->boolean('show_in_selector')->default(true);
$table->string('ticket_label')->nullable();
$table->timestamps();
});
Schema::create('variant_values', function (Blueprint $table): void {
$table->id();
$table->foreignId('variant_id')->constrained('variantes')->cascadeOnDelete();
$table->foreignId('item_attribute_id')->constrained('item_attributes')->cascadeOnDelete();
$table->text('value')->nullable();
$table->timestamps();
});
Schema::create('attachments', function (Blueprint $table): void {
$table->id();
$table->uuid('key')->unique();
$table->string('path');
$table->string('filename');
$table->string('type');
$table->string('mime_type');
$table->string('extension')->nullable();
$table->unsignedBigInteger('size')->default(0);
$table->timestamps();
});
Schema::create('catalog_items_attachments', function (Blueprint $table): void {
$table->id();
$table->foreignId('variant_id')->nullable()->constrained('variantes')->cascadeOnDelete();
$table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete();
$table->foreignId('attachment_id')->constrained('attachments')->cascadeOnDelete();
$table->unsignedInteger('orden')->default(0);
$table->boolean('is_enabled')->default(true);
});
Schema::create('featured_groups', function (Blueprint $table): void {
$table->id();
$table->string('tenant_code');
$table->string('code');
$table->string('source_type');
$table->unsignedBigInteger('category_id')->nullable();
$table->string('product_layout');
$table->string('group_layout');
$table->string('group_name');
$table->unsignedInteger('group_order')->default(0);
});
Schema::create('featured_items', function (Blueprint $table): void {
$table->id();
$table->foreignId('featured_group_id')->constrained('featured_groups')->cascadeOnDelete();
$table->foreignId('catalog_item_id')->constrained('catalog_items')->cascadeOnDelete();
$table->unsignedInteger('order')->default(0);
});
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace Tests\Feature\Seeders;
use Database\Seeders\MutualSmepAttributeSeeder;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;
class MutualSmepAttributeSeederTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Schema::create('tenants', function (Blueprint $table): void {
$table->string('codigo')->primary();
});
Schema::create('attribute', function (Blueprint $table): void {
$table->id();
$table->string('tenant_codigo');
$table->string('codigo');
$table->string('nombre');
$table->boolean('is_required')->default(false);
$table->json('metadata_schema')->nullable();
$table->string('type');
$table->timestamps();
$table->unique(['tenant_codigo', 'codigo']);
});
Schema::create('attribute_options', function (Blueprint $table): void {
$table->id();
$table->foreignId('attribute_id')->constrained('attribute')->cascadeOnDelete();
$table->string('value');
$table->string('label');
$table->unsignedInteger('sort_order')->default(0);
$table->json('metadata')->nullable();
$table->timestamps();
});
}
protected function tearDown(): void
{
Schema::dropIfExists('attribute_options');
Schema::dropIfExists('attribute');
Schema::dropIfExists('tenants');
parent::tearDown();
}
public function test_it_creates_the_attributes_and_options_idempotently(): void
{
$this->createTenant('mutual_smep');
$this->seed(MutualSmepAttributeSeeder::class);
$this->seed(MutualSmepAttributeSeeder::class);
$this->assertSame(5, DB::table('attribute')->where('tenant_codigo', 'mutual_smep')->count());
$this->assertSame(25, DB::table('attribute_options')->count());
$this->assertDatabaseHas('attribute', [
'tenant_codigo' => 'mutual_smep',
'codigo' => 'medida_cama',
'nombre' => 'Medida',
'type' => 'select',
'is_required' => false,
]);
$this->assertDatabaseHas('attribute_options', [
'value' => '512 GB',
'label' => '512 GB',
]);
}
public function test_down_only_removes_mutual_smep_attributes(): void
{
$this->createTenant('mutual_smep');
$this->createTenant('other');
DB::table('attribute')->insert([
'tenant_codigo' => 'other',
'codigo' => 'color',
'nombre' => 'Color',
'type' => 'select',
'created_at' => now(),
'updated_at' => now(),
]);
$seeder = app(MutualSmepAttributeSeeder::class);
$seeder->run();
$seeder->down();
$this->assertDatabaseMissing('attribute', ['tenant_codigo' => 'mutual_smep']);
$this->assertDatabaseHas('attribute', [
'tenant_codigo' => 'other',
'codigo' => 'color',
]);
}
public function test_the_data_migration_runs_and_rolls_back_the_attributes(): void
{
$this->createTenant('mutual_smep');
$migration = require database_path(
'migrations/2026_09_24_020000_seed_mutual_smep_attributes.php'
);
$migration->up();
$this->assertSame(5, DB::table('attribute')->where('tenant_codigo', 'mutual_smep')->count());
$this->assertSame(25, DB::table('attribute_options')->count());
$migration->down();
$this->assertDatabaseMissing('attribute', ['tenant_codigo' => 'mutual_smep']);
$this->assertSame(0, DB::table('attribute_options')->count());
}
private function createTenant(string $code): void
{
DB::table('tenants')->insert(['codigo' => $code]);
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace Tests\Feature\Seeders;
use App\Domains\Commerce\Catalog\Models\Category;
use Database\Seeders\MutualSmepCategorySeeder;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;
class MutualSmepCategorySeederTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Schema::create('tenants', function (Blueprint $table): void {
$table->string('codigo')->primary();
});
Schema::create('categorias', function (Blueprint $table): void {
$table->id();
$table->string('tenant_code')->nullable();
$table->unsignedBigInteger('categoria_id')->nullable();
$table->string('nombre');
$table->boolean('is_enabled')->default(true);
$table->timestamps();
});
}
protected function tearDown(): void
{
Schema::dropIfExists('categorias');
Schema::dropIfExists('tenants');
parent::tearDown();
}
public function test_it_creates_the_mutual_smep_category_hierarchy_idempotently(): void
{
$this->createTenant('mutual_smep');
$this->seed(MutualSmepCategorySeeder::class);
$this->seed(MutualSmepCategorySeeder::class);
$parents = Category::query()
->where('tenant_code', 'mutual_smep')
->whereNull('categoria_id')
->with('subCategories')
->get()
->keyBy('nombre');
$this->assertCount(6, $parents);
$this->assertCount(37, Category::query()->where('tenant_code', 'mutual_smep')->get());
$this->assertEqualsCanonicalizing([
'Colchones',
'Bases y Sommiers',
'Acolchados y Edredones',
'Frazadas y Mantas',
'Infantil y Cuna',
], $parents->get('Dormitorio y Blanco')->subCategories->pluck('nombre')->all());
$this->assertEqualsCanonicalizing([
'Celulares',
'Notebooks',
'Smart TV',
'Audio',
'Accesorios de Computación',
'Soportes para TV',
], $parents->get('Tecnología')->subCategories->pluck('nombre')->all());
}
public function test_down_removes_only_the_seeded_mutual_smep_hierarchy(): void
{
$this->createTenant('mutual_smep');
$this->createTenant('other');
Category::query()->create([
'tenant_code' => 'other',
'nombre' => 'Tecnología',
]);
$seeder = app(MutualSmepCategorySeeder::class);
$seeder->run();
$seeder->down();
$this->assertDatabaseMissing('categorias', [
'tenant_code' => 'mutual_smep',
]);
$this->assertDatabaseHas('categorias', [
'tenant_code' => 'other',
'nombre' => 'Tecnología',
]);
}
public function test_the_data_migration_runs_and_rolls_back_the_seeder(): void
{
$this->createTenant('mutual_smep');
$migration = require database_path(
'migrations/2026_09_24_010000_seed_mutual_smep_categories.php'
);
$migration->up();
$this->assertCount(37, Category::query()->where('tenant_code', 'mutual_smep')->get());
$migration->down();
$this->assertDatabaseMissing('categorias', [
'tenant_code' => 'mutual_smep',
]);
}
private function createTenant(string $code): void
{
DB::table('tenants')->insert([
'codigo' => $code,
]);
}
}