feat: refactor cart and purchase handling to support polymorphic buyable types and improve code readability

This commit is contained in:
2026-07-16 09:55:38 -03:00
parent c05206cc51
commit 6fc6ed1fac
14 changed files with 196 additions and 79 deletions

View File

@@ -60,7 +60,8 @@ class CartControllerTest extends TestCase
]);
$response = $this->postJson('/api/tenants/acme/cart/items', [
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 2,
]);
@@ -70,7 +71,8 @@ class CartControllerTest extends TestCase
->assertJsonPath('data.tenant_codigo', 'acme')
->assertJsonPath('data.items.0.cantidad', 2)
->assertJsonPath('data.items.0.precio_unitario', '49.90')
->assertJsonPath('data.items.0.product_id', $variant->product->id)
->assertJsonPath('data.items.0.buyable_type', 'variant')
->assertJsonPath('data.items.0.buyable_id', $variant->id)
->assertJsonPath('data.items.0.product.nombre', 'Shirt acme (Color: Red)')
->assertJsonPath('data.items.0.product.imagen', null)
->assertJsonPath('data.subtotal', '99.80');
@@ -82,7 +84,8 @@ class CartControllerTest extends TestCase
]);
$this->assertDatabaseHas('carrito_items', [
'producto_variante_id' => $variant->id,
'buyable_type' => ProductVariant::class,
'buyable_id' => $variant->id,
'cantidad' => 2,
]);
@@ -98,7 +101,8 @@ class CartControllerTest extends TestCase
$variant = $this->createVariantForTenant('acme', 12, '25.00');
$firstResponse = $this->postJson('/api/tenants/acme/cart/items', [
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 2,
]);
@@ -112,7 +116,8 @@ class CartControllerTest extends TestCase
[],
['HTTP_Accept' => 'application/json', 'CONTENT_TYPE' => 'application/json'],
json_encode([
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 3,
])
);
@@ -125,7 +130,8 @@ class CartControllerTest extends TestCase
$this->assertDatabaseCount('carritos', 1);
$this->assertDatabaseCount('carrito_items', 1);
$this->assertDatabaseHas('carrito_items', [
'producto_variante_id' => $variant->id,
'buyable_type' => ProductVariant::class,
'buyable_id' => $variant->id,
'cantidad' => 5,
]);
$this->assertDatabaseHas('productos_variantes', [
@@ -140,15 +146,17 @@ class CartControllerTest extends TestCase
$variant = $this->createVariantForTenant('acme', 10, '15.00');
$createResponse = $this->postJson('/api/tenants/acme/cart/items', [
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 2,
]);
$guestToken = $createResponse->getCookie('guest_token', false)?->getValue();
$cartItemId = $createResponse->json('data.items.0.id');
$response = $this->call(
'PATCH',
"/api/tenants/acme/cart/items/{$variant->id}",
"/api/tenants/acme/cart/items/{$cartItemId}",
[],
['guest_token' => $guestToken],
[],
@@ -164,7 +172,8 @@ class CartControllerTest extends TestCase
->assertJsonPath('data.subtotal', '75.00');
$this->assertDatabaseHas('carrito_items', [
'producto_variante_id' => $variant->id,
'buyable_type' => ProductVariant::class,
'buyable_id' => $variant->id,
'cantidad' => 5,
]);
$this->assertDatabaseHas('productos_variantes', [
@@ -179,15 +188,17 @@ class CartControllerTest extends TestCase
$variant = $this->createVariantForTenant('acme', 10, '15.00');
$createResponse = $this->postJson('/api/tenants/acme/cart/items', [
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 4,
]);
$guestToken = $createResponse->getCookie('guest_token', false)?->getValue();
$cartItemId = $createResponse->json('data.items.0.id');
$response = $this->call(
'DELETE',
"/api/tenants/acme/cart/items/{$variant->id}",
"/api/tenants/acme/cart/items/{$cartItemId}",
[],
['guest_token' => $guestToken],
[],
@@ -216,14 +227,16 @@ class CartControllerTest extends TestCase
$this->actingAs($user)
->postJson('/api/tenants/acme/cart/items', [
'product_variant_id' => $acmeVariantA->id,
'buyable_type' => 'variant',
'buyable_id' => $acmeVariantA->id,
'cantidad' => 1,
])
->assertOk();
$this->actingAs($user)
->postJson('/api/tenants/acme/cart/items', [
'product_variant_id' => $acmeVariantB->id,
'buyable_type' => 'variant',
'buyable_id' => $acmeVariantB->id,
'cantidad' => 2,
])
->assertOk()
@@ -231,7 +244,8 @@ class CartControllerTest extends TestCase
$this->actingAs($user)
->postJson('/api/tenants/globex/cart/items', [
'product_variant_id' => $globexVariant->id,
'buyable_type' => 'variant',
'buyable_id' => $globexVariant->id,
'cantidad' => 1,
])
->assertOk();
@@ -253,7 +267,8 @@ class CartControllerTest extends TestCase
$otherVariant = $this->createVariantForTenant('globex', 10, '20.00');
$this->postJson('/api/tenants/acme/cart/items', [
'product_variant_id' => $otherVariant->id,
'buyable_type' => 'variant',
'buyable_id' => $otherVariant->id,
'cantidad' => 1,
])->assertNotFound();
}
@@ -275,16 +290,19 @@ class CartControllerTest extends TestCase
$variant = $this->createVariantForTenant('acme', 2, '10.00');
$this->postJson('/api/tenants/acme/cart/items', [
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 0,
])->assertUnprocessable()->assertJsonValidationErrors(['cantidad']);
$response = $this->postJson('/api/tenants/acme/cart/items', [
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 2,
]);
$guestToken = $response->getCookie('guest_token', false)?->getValue();
$cartItemId = $response->json('data.items.0.id');
$response1 = $this->call(
'POST',
@@ -294,7 +312,8 @@ class CartControllerTest extends TestCase
[],
['HTTP_Accept' => 'application/json', 'CONTENT_TYPE' => 'application/json'],
json_encode([
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 1,
])
);
@@ -305,7 +324,7 @@ class CartControllerTest extends TestCase
$response2 = $this->call(
'PATCH',
"/api/tenants/acme/cart/items/{$variant->id}",
"/api/tenants/acme/cart/items/{$cartItemId}",
[],
['guest_token' => $guestToken],
[],
@@ -320,6 +339,32 @@ class CartControllerTest extends TestCase
->assertJsonValidationErrors(['cantidad' => 'El máximo que se puede agregar es 2.']);
}
public function test_it_only_accepts_buyable_identity_when_adding_an_item(): void
{
$variant = $this->createVariantForTenant('acme', 10, '10.00');
$this->postJson('/api/tenants/acme/cart/items', [
'product_variant_id' => $variant->id,
'cantidad' => 1,
])->assertUnprocessable()
->assertJsonValidationErrors(['buyable_type', 'buyable_id']);
$createResponse = $this->postJson('/api/tenants/acme/cart/items', [
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 1,
])->assertOk();
$cartItemId = $createResponse->json('data.items.0.id');
$this->patchJson("/api/tenants/acme/cart/items/{$cartItemId}", [
'cantidad' => 2,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
])->assertUnprocessable()
->assertJsonValidationErrors(['buyable_type', 'buyable_id']);
}
public function test_unlimited_inventory_can_be_reserved_updated_and_released_without_real_stock(): void
{
$variant = $this->createVariantForTenant(
@@ -331,11 +376,13 @@ class CartControllerTest extends TestCase
);
$response = $this->postJson('/api/tenants/acme/cart/items', [
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 100,
])->assertOk();
$guestToken = $response->getCookie('guest_token', false)?->getValue();
$cartItemId = $response->json('data.items.0.id');
$this->assertDatabaseHas('productos_variantes', [
'id' => $variant->id,
'stock_real' => 0,
@@ -344,7 +391,7 @@ class CartControllerTest extends TestCase
$this->call(
'PATCH',
"/api/tenants/acme/cart/items/{$variant->id}",
"/api/tenants/acme/cart/items/{$cartItemId}",
[],
['guest_token' => $guestToken],
[],
@@ -360,7 +407,7 @@ class CartControllerTest extends TestCase
$this->call(
'DELETE',
"/api/tenants/acme/cart/items/{$variant->id}",
"/api/tenants/acme/cart/items/{$cartItemId}",
[],
['guest_token' => $guestToken],
[],

View File

@@ -113,7 +113,8 @@ class TelepagosWebhookTest extends TestCase
$this->assertDatabaseHas('compra_items', [
'compra_id' => $matchingPurchase->id,
'producto_variante_id' => $variant->id,
'buyable_type' => ProductVariant::class,
'buyable_id' => $variant->id,
'cantidad' => 1,
'total' => 50,
]);
@@ -201,7 +202,7 @@ class TelepagosWebhookTest extends TestCase
'status' => 'active',
]);
$cart->addItem($variantId, $quantity);
$cart->addItem(ProductVariant::class, $variantId, $quantity);
/** @var CheckoutService $checkoutService */
$checkoutService = app(CheckoutService::class);

View File

@@ -51,7 +51,8 @@ class StorePurchaseTest extends TestCase
$cartResponse = $this->actingAs($user, 'sanctum')
->postJson('/api/tenants/sonder/cart/items', [
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 2,
])
->assertOk();
@@ -111,7 +112,8 @@ class StorePurchaseTest extends TestCase
]);
$this->assertDatabaseHas('carrito_items', [
'cart_id' => $cartId,
'producto_variante_id' => $variant->id,
'buyable_type' => ProductVariant::class,
'buyable_id' => $variant->id,
'cantidad' => 2,
]);
$this->assertDatabaseHas('productos_variantes', [
@@ -131,7 +133,8 @@ class StorePurchaseTest extends TestCase
$cartId = $this->actingAs($user, 'sanctum')
->postJson('/api/tenants/sonder/cart/items', [
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 2,
])
->assertOk()
@@ -280,7 +283,8 @@ class StorePurchaseTest extends TestCase
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
$purchase->items()->create([
'producto_variante_id' => $variant->id,
'buyable_type' => ProductVariant::class,
'buyable_id' => $variant->id,
'cantidad' => 1,
'precio_unitario' => '50.00',
'discount_total' => null,
@@ -326,7 +330,8 @@ class StorePurchaseTest extends TestCase
$cartId = $this->actingAs($owner, 'sanctum')
->postJson('/api/tenants/sonder/cart/items', [
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 1,
])
->assertOk()
@@ -352,7 +357,8 @@ class StorePurchaseTest extends TestCase
$cartId = $this->actingAs($user, 'sanctum')
->postJson('/api/tenants/globex/cart/items', [
'product_variant_id' => $variant->id,
'buyable_type' => 'variant',
'buyable_id' => $variant->id,
'cantidad' => 1,
])
->assertOk()
@@ -492,7 +498,7 @@ class StorePurchaseTest extends TestCase
'status' => 'active',
]);
$cart->addItem($variant->id, $quantity);
$cart->addItem(ProductVariant::class, $variant->id, $quantity);
return app(CheckoutService::class)->startCheckout($tenant, $user->id, [
'cart_id' => $cart->id,