feat(cart): implement cart management with add, update, and remove item functionalities
This commit is contained in:
303
tests/Feature/Cart/CartControllerTest.php
Normal file
303
tests/Feature/Cart/CartControllerTest.php
Normal file
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Cart;
|
||||
|
||||
use App\Domains\Catalog\Models\Product;
|
||||
use App\Domains\Catalog\Models\ProductAttribute;
|
||||
use App\Domains\Catalog\Models\ProductVariant;
|
||||
use App\Domains\Catalog\Models\ProductVariantDefinition;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class CartControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_it_returns_an_empty_guest_cart_when_cart_does_not_exist(): void
|
||||
{
|
||||
Tenant::create([
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'dominio' => 'acme.com',
|
||||
]);
|
||||
|
||||
$this->getJson('/api/tenants/acme/cart')
|
||||
->assertOk()
|
||||
->assertJson([
|
||||
'id' => null,
|
||||
'tenant_codigo' => 'acme',
|
||||
'status' => 'active',
|
||||
'items' => [],
|
||||
'subtotal' => '0.00',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_creates_a_guest_cart_and_returns_the_cart_snapshot(): void
|
||||
{
|
||||
$variant = $this->createVariantForTenant('acme', 10, '49.90');
|
||||
$attribute = ProductAttribute::query()->create([
|
||||
'tenant_codigo' => 'acme',
|
||||
'codigo' => 'color',
|
||||
'nombre' => 'Color',
|
||||
'type' => 'text',
|
||||
]);
|
||||
|
||||
ProductVariantDefinition::query()->create([
|
||||
'producto_variante_id' => $variant->id,
|
||||
'attribute_id' => $attribute->id,
|
||||
'value' => 'Red',
|
||||
]);
|
||||
|
||||
$response = $this->postJson('/api/tenants/acme/cart/items', [
|
||||
'product_variant_id' => $variant->id,
|
||||
'cantidad' => 2,
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertOk()
|
||||
->assertCookie('guest_token')
|
||||
->assertJsonPath('tenant_codigo', 'acme')
|
||||
->assertJsonPath('items.0.cantidad', 2)
|
||||
->assertJsonPath('items.0.precio_unitario', '49.90')
|
||||
->assertJsonPath('items.0.subtotal', '99.80')
|
||||
->assertJsonPath('items.0.product.id', $variant->product->id)
|
||||
->assertJsonPath('items.0.variant.id', $variant->id)
|
||||
->assertJsonPath('items.0.variant.definitions.0.attribute.codigo', 'color')
|
||||
->assertJsonPath('subtotal', '99.80');
|
||||
|
||||
$this->assertDatabaseHas('carritos', [
|
||||
'tenant_codigo' => 'acme',
|
||||
'guest_token' => $response->getCookie('guest_token')?->getValue(),
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('carrito_items', [
|
||||
'producto_variante_id' => $variant->id,
|
||||
'cantidad' => 2,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('productos_variantes', [
|
||||
'id' => $variant->id,
|
||||
'stock' => 8,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_merges_quantities_when_the_same_guest_adds_the_same_variant_twice(): void
|
||||
{
|
||||
$variant = $this->createVariantForTenant('acme', 12, '25.00');
|
||||
|
||||
$firstResponse = $this->postJson('/api/tenants/acme/cart/items', [
|
||||
'product_variant_id' => $variant->id,
|
||||
'cantidad' => 2,
|
||||
]);
|
||||
|
||||
$guestToken = $firstResponse->getCookie('guest_token')?->getValue();
|
||||
|
||||
$this->withCookie('guest_token', $guestToken)
|
||||
->postJson('/api/tenants/acme/cart/items', [
|
||||
'product_variant_id' => $variant->id,
|
||||
'cantidad' => 3,
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('items.0.cantidad', 5)
|
||||
->assertJsonPath('items.0.subtotal', '125.00')
|
||||
->assertJsonPath('subtotal', '125.00');
|
||||
|
||||
$this->assertDatabaseCount('carritos', 1);
|
||||
$this->assertDatabaseCount('carrito_items', 1);
|
||||
$this->assertDatabaseHas('carrito_items', [
|
||||
'producto_variante_id' => $variant->id,
|
||||
'cantidad' => 5,
|
||||
]);
|
||||
$this->assertDatabaseHas('productos_variantes', [
|
||||
'id' => $variant->id,
|
||||
'stock' => 7,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_updates_item_quantity_and_adjusts_stock(): void
|
||||
{
|
||||
$variant = $this->createVariantForTenant('acme', 10, '15.00');
|
||||
|
||||
$createResponse = $this->postJson('/api/tenants/acme/cart/items', [
|
||||
'product_variant_id' => $variant->id,
|
||||
'cantidad' => 2,
|
||||
]);
|
||||
|
||||
$guestToken = $createResponse->getCookie('guest_token')?->getValue();
|
||||
|
||||
$this->withCookie('guest_token', $guestToken)
|
||||
->patchJson("/api/tenants/acme/cart/items/{$variant->id}", [
|
||||
'cantidad' => 5,
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('items.0.cantidad', 5)
|
||||
->assertJsonPath('items.0.subtotal', '75.00')
|
||||
->assertJsonPath('subtotal', '75.00');
|
||||
|
||||
$this->assertDatabaseHas('carrito_items', [
|
||||
'producto_variante_id' => $variant->id,
|
||||
'cantidad' => 5,
|
||||
]);
|
||||
$this->assertDatabaseHas('productos_variantes', [
|
||||
'id' => $variant->id,
|
||||
'stock' => 5,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_removes_an_item_and_restores_stock(): void
|
||||
{
|
||||
$variant = $this->createVariantForTenant('acme', 10, '15.00');
|
||||
|
||||
$createResponse = $this->postJson('/api/tenants/acme/cart/items', [
|
||||
'product_variant_id' => $variant->id,
|
||||
'cantidad' => 4,
|
||||
]);
|
||||
|
||||
$guestToken = $createResponse->getCookie('guest_token')?->getValue();
|
||||
|
||||
$this->withCookie('guest_token', $guestToken)
|
||||
->deleteJson("/api/tenants/acme/cart/items/{$variant->id}")
|
||||
->assertOk()
|
||||
->assertJsonPath('items', [])
|
||||
->assertJsonPath('subtotal', '0.00');
|
||||
|
||||
$this->assertDatabaseCount('carrito_items', 0);
|
||||
$this->assertDatabaseHas('productos_variantes', [
|
||||
'id' => $variant->id,
|
||||
'stock' => 10,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_authenticated_users_reuse_the_same_cart_per_tenant_and_get_a_new_one_for_another_tenant(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$acmeVariantA = $this->createVariantForTenant('acme', 10, '10.00');
|
||||
$acmeVariantB = $this->createVariantForTenant('acme', 8, '20.00', 'hoodie');
|
||||
$globexVariant = $this->createVariantForTenant('globex', 6, '30.00');
|
||||
|
||||
$this->actingAs($user)
|
||||
->postJson('/api/tenants/acme/cart/items', [
|
||||
'product_variant_id' => $acmeVariantA->id,
|
||||
'cantidad' => 1,
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
$this->actingAs($user)
|
||||
->postJson('/api/tenants/acme/cart/items', [
|
||||
'product_variant_id' => $acmeVariantB->id,
|
||||
'cantidad' => 2,
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('subtotal', '50.00');
|
||||
|
||||
$this->actingAs($user)
|
||||
->postJson('/api/tenants/globex/cart/items', [
|
||||
'product_variant_id' => $globexVariant->id,
|
||||
'cantidad' => 1,
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
$this->assertDatabaseCount('carritos', 2);
|
||||
$this->assertDatabaseHas('carritos', [
|
||||
'tenant_codigo' => 'acme',
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
$this->assertDatabaseHas('carritos', [
|
||||
'tenant_codigo' => 'globex',
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_rejects_variants_from_another_tenant(): void
|
||||
{
|
||||
$this->createVariantForTenant('acme', 10, '10.00');
|
||||
$otherVariant = $this->createVariantForTenant('globex', 10, '20.00');
|
||||
|
||||
$this->postJson('/api/tenants/acme/cart/items', [
|
||||
'product_variant_id' => $otherVariant->id,
|
||||
'cantidad' => 1,
|
||||
])->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_it_returns_not_found_when_the_cart_item_does_not_exist_for_update_or_delete(): void
|
||||
{
|
||||
$variant = $this->createVariantForTenant('acme', 10, '10.00');
|
||||
|
||||
$this->patchJson("/api/tenants/acme/cart/items/{$variant->id}", [
|
||||
'cantidad' => 2,
|
||||
])->assertNotFound();
|
||||
|
||||
$this->deleteJson("/api/tenants/acme/cart/items/{$variant->id}")
|
||||
->assertNotFound();
|
||||
}
|
||||
|
||||
public function test_it_validates_quantity_and_stock_constraints(): void
|
||||
{
|
||||
$variant = $this->createVariantForTenant('acme', 2, '10.00');
|
||||
|
||||
$this->postJson('/api/tenants/acme/cart/items', [
|
||||
'product_variant_id' => $variant->id,
|
||||
'cantidad' => 0,
|
||||
])->assertUnprocessable()->assertJsonValidationErrors(['cantidad']);
|
||||
|
||||
$response = $this->postJson('/api/tenants/acme/cart/items', [
|
||||
'product_variant_id' => $variant->id,
|
||||
'cantidad' => 2,
|
||||
]);
|
||||
|
||||
$guestToken = $response->getCookie('guest_token')?->getValue();
|
||||
|
||||
$this->withCookie('guest_token', $guestToken)
|
||||
->postJson('/api/tenants/acme/cart/items', [
|
||||
'product_variant_id' => $variant->id,
|
||||
'cantidad' => 1,
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['cantidad']);
|
||||
|
||||
$this->withCookie('guest_token', $guestToken)
|
||||
->patchJson("/api/tenants/acme/cart/items/{$variant->id}", [
|
||||
'cantidad' => 3,
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['cantidad']);
|
||||
}
|
||||
|
||||
protected function createVariantForTenant(
|
||||
string $tenantCode,
|
||||
int $stock,
|
||||
string $price,
|
||||
string $slugPrefix = 'shirt',
|
||||
): ProductVariant {
|
||||
Tenant::query()->firstOrCreate(
|
||||
['codigo' => $tenantCode],
|
||||
['nombre' => ucfirst($tenantCode), 'dominio' => "{$tenantCode}.com"],
|
||||
);
|
||||
|
||||
$category = \App\Domains\Catalog\Models\Category::query()->create([
|
||||
'tenant_code' => $tenantCode,
|
||||
'nombre' => "{$slugPrefix} category {$tenantCode}",
|
||||
]);
|
||||
|
||||
$product = Product::query()->create([
|
||||
'tenant_codigo' => $tenantCode,
|
||||
'categoria_id' => $category->id,
|
||||
'slug' => "{$slugPrefix}-{$tenantCode}-".Product::query()->count(),
|
||||
'nombre' => ucfirst($slugPrefix)." {$tenantCode}",
|
||||
'descripcion' => 'Test product',
|
||||
'precio' => $price,
|
||||
]);
|
||||
|
||||
return ProductVariant::query()->create([
|
||||
'producto_id' => $product->id,
|
||||
'slug' => "{$slugPrefix}-variant-".ProductVariant::query()->count(),
|
||||
'nombre' => ucfirst($slugPrefix).' Variant',
|
||||
'stock' => $stock,
|
||||
'descripcion' => 'Test variant',
|
||||
'precio' => $price,
|
||||
])->load('product');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user