createTenant('catalog-index'); $row = $this->createGroup($tenant, ProductLayout::Row, 'Row', 2); $cart = $this->createGroup( $tenant, ProductLayout::ColumnWithCart, 'Cart', 1, GroupLayout::SimpleVertical, ); $directInventory = Inventory::query()->create([ 'real_stock' => 10, 'reserved_stock' => 2, ]); $directItem = $this->createItem($tenant, 'Direct', $directInventory); $row->featuredItems()->create(['catalog_item_id' => $directItem->id]); $variantItem = $this->createItem($tenant, 'Variants'); $firstInventory = Inventory::query()->create([ 'real_stock' => 5, 'reserved_stock' => 1, ]); $secondInventory = Inventory::query()->create([ 'real_stock' => 4, 'reserved_stock' => 1, ]); $variantItem->variants()->create(['inventory_id' => $firstInventory->id]); $variantItem->variants()->create(['inventory_id' => $secondInventory->id]); $cart->featuredItems()->create(['catalog_item_id' => $variantItem->id]); $response = $this->getJson("/api/tenants/{$tenant->codigo}/catalog"); $response ->assertOk() ->assertJsonCount(2) ->assertJsonPath('0.title', 'Cart') ->assertJsonPath('0.layout', ProductLayout::ColumnWithCart->value) ->assertJsonPath('0.group_layout', GroupLayout::SimpleVertical->value) ->assertJsonPath('0.items.0.nombre', 'Variants') ->assertJsonPath('0.items.0.descripcion', 'Variants description') ->assertJsonPath('0.items.0.precio', '100.00') ->assertJsonPath('0.items.0.stock_tecnico', 7) ->assertJsonCount(2, '0.items.0.variants') ->assertJsonPath('0.items.0.variants.0.stock_tecnico', 4) ->assertJsonPath('0.items.0.variants.1.stock_tecnico', 3) ->assertJsonPath('1.title', 'Row') ->assertJsonPath('1.items.data.0.stock_tecnico', 8) ->assertJsonCount(0, '1.items.data.0.variants'); } public function test_column_with_image_uses_item_image_then_variant_image_then_null(): void { Storage::fake('s3'); $tenant = $this->createTenant('catalog-images'); $group = $this->createGroup($tenant, ProductLayout::ColumnWithImage, 'Images'); $directItem = $this->createItem($tenant, 'Direct image'); $directImage = $this->createAttachment('direct'); $directItem->attachments()->attach($directImage, ['orden' => 0]); $group->featuredItems()->create([ 'catalog_item_id' => $directItem->id, 'order' => 0, ]); $variantItem = $this->createItem($tenant, 'Variant image'); $variantInventory = Inventory::query()->create(); $variant = $variantItem->variants()->create(['inventory_id' => $variantInventory->id]); $variantImage = $this->createAttachment('variant'); $variant->attachments()->attach($variantImage, ['orden' => 0]); $group->featuredItems()->create([ 'catalog_item_id' => $variantItem->id, 'order' => 1, ]); $emptyItem = $this->createItem($tenant, 'No image'); $group->featuredItems()->create([ 'catalog_item_id' => $emptyItem->id, 'order' => 2, ]); $response = $this->getJson("/api/tenants/{$tenant->codigo}/catalog"); $response ->assertOk() ->assertJsonPath('0.layout', ProductLayout::ColumnWithImage->value) ->assertJsonPath('0.items.data.0.nombre', 'Direct image') ->assertJsonPath('0.items.data.0.precio', '100.00') ->assertJsonPath('0.items.data.0.image', fn (?string $url): bool => str_contains($url ?? '', 'direct.png')) ->assertJsonPath('0.items.data.1.image', fn (?string $url): bool => str_contains($url ?? '', 'variant.png')) ->assertJsonPath('0.items.data.2.image', null); } public function test_index_always_returns_page_one_and_group_endpoint_returns_other_pages(): void { $tenant = $this->createTenant('catalog-pagination'); $group = $this->createGroup($tenant, ProductLayout::Row, 'Paginated'); foreach (range(1, 13) as $number) { $item = $this->createItem($tenant, "Item {$number}"); $group->featuredItems()->create([ 'catalog_item_id' => $item->id, 'order' => $number, ]); } $this->getJson("/api/tenants/{$tenant->codigo}/catalog?page=2") ->assertOk() ->assertJsonPath('0.items.meta.current_page', 1) ->assertJsonPath('0.items.meta.last_page', 2) ->assertJsonPath('0.items.meta.per_page', 12) ->assertJsonPath('0.items.meta.total', 13) ->assertJsonCount(12, '0.items.data') ->assertJsonPath('0.items.data.0.nombre', 'Item 1'); $this->getJson( "/api/tenants/{$tenant->codigo}/catalog/featured-groups/{$group->id}/items?page=2" ) ->assertOk() ->assertJsonPath('meta.current_page', 2) ->assertJsonPath('meta.last_page', 2) ->assertJsonCount(1, 'data') ->assertJsonPath('data.0.nombre', 'Item 13'); } public function test_non_paginated_group_layouts_return_all_items_as_a_plain_array(): void { $tenant = $this->createTenant('catalog-simple-layouts'); $layouts = [ GroupLayout::Simple, GroupLayout::SimpleVertical, GroupLayout::Carousel, ]; foreach ($layouts as $order => $groupLayout) { $group = $this->createGroup( $tenant, ProductLayout::Row, $groupLayout->value, $order, $groupLayout, ); foreach (range(1, 13) as $number) { $item = $this->createItem($tenant, "{$groupLayout->value} Item {$number}"); $group->featuredItems()->create([ 'catalog_item_id' => $item->id, 'order' => $number, ]); } } $response = $this->getJson("/api/tenants/{$tenant->codigo}/catalog")->assertOk(); $groups = $response->json(); foreach ($layouts as $index => $groupLayout) { $this->assertSame($groupLayout->value, $groups[$index]['group_layout']); $this->assertCount(13, $groups[$index]['items']); $this->assertSame( "{$groupLayout->value} Item 1", $groups[$index]['items'][0]['nombre'], ); $this->assertArrayNotHasKey('data', $groups[$index]['items']); $this->assertArrayNotHasKey('meta', $groups[$index]['items']); } $carouselGroup = FeaturedGroup::query() ->where('tenant_code', $tenant->codigo) ->where('group_layout', GroupLayout::Carousel) ->sole(); $this->getJson( "/api/tenants/{$tenant->codigo}/catalog/featured-groups/{$carouselGroup->id}/items?page=2" ) ->assertOk() ->assertJsonCount(13) ->assertJsonPath('0.nombre', 'carousel Item 1'); } private function createGroup( Tenant $tenant, ProductLayout $layout, string $name, int $order = 0, GroupLayout $groupLayout = GroupLayout::Paginated, ): FeaturedGroup { return FeaturedGroup::query()->create([ 'tenant_code' => $tenant->codigo, 'product_layout' => $layout, 'group_layout' => $groupLayout, 'group_name' => $name, 'group_order' => $order, ]); } private function createItem( Tenant $tenant, string $name, ?Inventory $inventory = null, ): CatalogItem { return CatalogItem::query()->create([ 'tenant_code' => $tenant->codigo, 'inventory_id' => $inventory?->id, 'slug' => str($name)->slug()->toString(), 'nombre' => $name, 'descripcion' => "{$name} description", 'precio' => 100, ]); } 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}.local", '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', ]); } }