feat: implement product controller, service, and feature tests with updated API collection.
This commit is contained in:
parent
14f3906ef0
commit
46e7c27ae7
|
|
@ -15,19 +15,10 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
public function index(Tenant $tenant): JsonResponse
|
||||
public function index(Tenant $tenant, ProductService $productService): JsonResponse
|
||||
{
|
||||
return ProductResource::collection(
|
||||
Product::query()
|
||||
->where('tenant_codigo', $tenant->codigo)
|
||||
->with([
|
||||
'attachments' => fn ($query) => $query->orderBy('attachments.id')->limit(1),
|
||||
'brand',
|
||||
'category',
|
||||
'attributes.options',
|
||||
])
|
||||
->latest()
|
||||
->paginateFromRequest()
|
||||
$productService->getProductos($tenant)
|
||||
)->response();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -244,4 +244,37 @@ class ProductService
|
|||
Product::deleteAttribute($attribute);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products for a tenant with resolved first image (with fallback to first variant's first image).
|
||||
*/
|
||||
public function getProductos(Tenant $tenant): \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
{
|
||||
$products = Product::query()
|
||||
->where('tenant_codigo', $tenant->codigo)
|
||||
->with([
|
||||
'attachments' => fn ($query) => $query->orderBy('attachments.id'),
|
||||
'brand',
|
||||
'category',
|
||||
'variants.attachments' => fn ($query) => $query->orderBy('attachments.id'),
|
||||
])
|
||||
->latest()
|
||||
->paginateFromRequest();
|
||||
|
||||
foreach ($products as $product) {
|
||||
$resolvedAttachment = null;
|
||||
if ($product->attachments->isNotEmpty()) {
|
||||
$resolvedAttachment = $product->attachments->first();
|
||||
} else {
|
||||
$firstVariant = $product->variants->sortBy('id')->first();
|
||||
if ($firstVariant && $firstVariant->attachments->isNotEmpty()) {
|
||||
$resolvedAttachment = $firstVariant->attachments->first();
|
||||
}
|
||||
}
|
||||
|
||||
$product->setRelation('attachments', $resolvedAttachment ? collect([$resolvedAttachment]) : collect());
|
||||
}
|
||||
|
||||
return $products;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,4 +90,137 @@ class ProductVariantAttachmentTest extends TestCase
|
|||
$this->assertTrue($variant->attachments->contains($attachment1));
|
||||
$this->assertTrue($variant->attachments->contains($attachment2));
|
||||
}
|
||||
|
||||
public function test_getProductos_listing_image_fallback(): void
|
||||
{
|
||||
// 1. Create Tenant
|
||||
$hdrKey = (string) Str::uuid();
|
||||
$ftrKey = (string) Str::uuid();
|
||||
$headerAttachment = Attachment::create([
|
||||
'key' => $hdrKey,
|
||||
'path' => 'tenants/' . $hdrKey . '.png',
|
||||
'filename' => 'logo_header.png',
|
||||
'type' => \App\Domains\Attachable\Enums\AttachmentType::Image,
|
||||
'mime_type' => 'image/png',
|
||||
]);
|
||||
$footerAttachment = Attachment::create([
|
||||
'key' => $ftrKey,
|
||||
'path' => 'tenants/' . $ftrKey . '.png',
|
||||
'filename' => 'logo_footer.png',
|
||||
'type' => \App\Domains\Attachable\Enums\AttachmentType::Image,
|
||||
'mime_type' => 'image/png',
|
||||
]);
|
||||
|
||||
$tenant = Tenant::create([
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme Inc.',
|
||||
'dominio' => 'acme.com',
|
||||
'primary_color' => '#ffffff',
|
||||
'secondary_color' => '#ffffff',
|
||||
'danger_color' => '#ffffff',
|
||||
'header_footer_bg_color' => '#ffffff',
|
||||
'header_logo_id' => $headerAttachment->id,
|
||||
'footer_logo_id' => $footerAttachment->id,
|
||||
]);
|
||||
|
||||
// 2. Create Product 1 (has 2 attachments itself)
|
||||
$product1 = Product::create([
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
'categoria_id' => 1,
|
||||
'slug' => 'product-1',
|
||||
'nombre' => 'Product 1',
|
||||
'precio' => 10.00,
|
||||
]);
|
||||
$p1Attachment1 = Attachment::create([
|
||||
'key' => (string) Str::uuid(),
|
||||
'path' => 'attachments/p1_1.png',
|
||||
'filename' => 'p1_1.png',
|
||||
'type' => \App\Domains\Attachable\Enums\AttachmentType::Image,
|
||||
'mime_type' => 'image/png',
|
||||
]);
|
||||
$p1Attachment2 = Attachment::create([
|
||||
'key' => (string) Str::uuid(),
|
||||
'path' => 'attachments/p1_2.png',
|
||||
'filename' => 'p1_2.png',
|
||||
'type' => \App\Domains\Attachable\Enums\AttachmentType::Image,
|
||||
'mime_type' => 'image/png',
|
||||
]);
|
||||
$product1->attachments()->attach([$p1Attachment1->id, $p1Attachment2->id]);
|
||||
|
||||
// 3. Create Product 2 (no attachments itself, has 2 variants: first variant has 2 attachments, second has 1)
|
||||
$product2 = Product::create([
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
'categoria_id' => 1,
|
||||
'slug' => 'product-2',
|
||||
'nombre' => 'Product 2',
|
||||
'precio' => 20.00,
|
||||
]);
|
||||
$v1 = ProductVariant::create([
|
||||
'producto_id' => $product2->id,
|
||||
'slug' => 'p2-v1',
|
||||
'nombre' => 'P2 V1',
|
||||
'stock' => 10,
|
||||
'precio' => 20.00,
|
||||
]);
|
||||
$v2 = ProductVariant::create([
|
||||
'producto_id' => $product2->id,
|
||||
'slug' => 'p2-v2',
|
||||
'nombre' => 'P2 V2',
|
||||
'stock' => 5,
|
||||
'precio' => 20.00,
|
||||
]);
|
||||
$v1Attachment1 = Attachment::create([
|
||||
'key' => (string) Str::uuid(),
|
||||
'path' => 'attachments/v1_1.png',
|
||||
'filename' => 'v1_1.png',
|
||||
'type' => \App\Domains\Attachable\Enums\AttachmentType::Image,
|
||||
'mime_type' => 'image/png',
|
||||
]);
|
||||
$v1Attachment2 = Attachment::create([
|
||||
'key' => (string) Str::uuid(),
|
||||
'path' => 'attachments/v1_2.png',
|
||||
'filename' => 'v1_2.png',
|
||||
'type' => \App\Domains\Attachable\Enums\AttachmentType::Image,
|
||||
'mime_type' => 'image/png',
|
||||
]);
|
||||
$v1->attachments()->attach([$v1Attachment1->id, $v1Attachment2->id]);
|
||||
|
||||
$v2Attachment = Attachment::create([
|
||||
'key' => (string) Str::uuid(),
|
||||
'path' => 'attachments/v2_1.png',
|
||||
'filename' => 'v2_1.png',
|
||||
'type' => \App\Domains\Attachable\Enums\AttachmentType::Image,
|
||||
'mime_type' => 'image/png',
|
||||
]);
|
||||
$v2->attachments()->attach([$v2Attachment->id]);
|
||||
|
||||
// 4. Create Product 3 (no attachments, no variants)
|
||||
$product3 = Product::create([
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
'categoria_id' => 1,
|
||||
'slug' => 'product-3',
|
||||
'nombre' => 'Product 3',
|
||||
'precio' => 30.00,
|
||||
]);
|
||||
|
||||
// Call the listing API
|
||||
$response = $this->getJson("/api/tenants/{$tenant->codigo}/productos");
|
||||
$response->assertOk();
|
||||
|
||||
// Check response data
|
||||
// Since we order products by latest() (created_at desc), the order is: Product 3, Product 2, Product 1.
|
||||
$data = $response->json('data');
|
||||
$this->assertCount(3, $data);
|
||||
|
||||
// Product 1 (index 0) has attachments -> should have exactly its first attachment (p1Attachment1)
|
||||
$this->assertCount(1, $data[0]['images']);
|
||||
$this->assertStringContainsString($p1Attachment1->path, $data[0]['images'][0]);
|
||||
|
||||
// Product 2 (index 1) has no attachments, falls back to first variant (v1) first attachment (v1Attachment1) -> should have exactly 1 image
|
||||
$this->assertCount(1, $data[1]['images']);
|
||||
$this->assertStringContainsString($v1Attachment1->path, $data[1]['images'][0]);
|
||||
|
||||
// Product 3 (index 2) has no attachments, no variants -> should have empty images
|
||||
$this->assertEmpty($data[2]['images']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue