refactor(checkout): disable purchase item editing

This commit is contained in:
2026-08-21 11:03:48 -03:00
parent 4e7e42d16e
commit 79e721ae63
18 changed files with 172 additions and 1191 deletions

View File

@@ -30,7 +30,7 @@ class StorePurchaseTest extends TestCase
Queue::fake();
}
public function test_it_starts_checkout_from_cart_without_materializing_purchase_items(): void
public function test_it_starts_checkout_from_cart_with_purchase_item_snapshots(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update([
@@ -90,7 +90,7 @@ class StorePurchaseTest extends TestCase
$response->assertJsonPath('data.email', null);
$response->assertJsonPath('data.tenant_codigo', 'sonder');
$response->assertJsonPath('data.status', Purchase::STATUS_CREATED);
$response->assertJsonPath('data.items_source', 'cart');
$response->assertJsonPath('data.items_source', 'purchase');
$response->assertJsonCount(1, 'data.items');
$response->assertJsonPath('data.subtotal', '100.00');
$response->assertJsonPath('data.total', '100.00');
@@ -109,7 +109,14 @@ class StorePurchaseTest extends TestCase
'status' => Purchase::STATUS_CREATED,
'total' => 100,
]);
$this->assertDatabaseMissing('compra_items', ['compra_id' => $purchaseId]);
$this->assertDatabaseHas('compra_items', [
'compra_id' => $purchaseId,
'source_catalog_item_id' => $catalogItem->id,
'source_variant_id' => $variant->id,
'cantidad' => 2,
'precio_unitario' => '50.00',
'total' => '100.00',
]);
$this->assertDatabaseHas('stock_reservations', [
'inventory_id' => $inventory->id,
'purchase_id' => $purchaseId,
@@ -142,10 +149,10 @@ class StorePurchaseTest extends TestCase
$this->actingAs($user, 'sanctum')
->getJson("/api/tenants/sonder/compras/{$purchaseId}")
->assertOk()
->assertJsonPath('data.items_source', 'cart')
->assertJsonPath('data.items.0.item_details.nombre', 'Updated Product')
->assertJsonPath('data.items.0.unit_price', '75.00')
->assertJsonPath('data.total', '150.00');
->assertJsonPath('data.items_source', 'purchase')
->assertJsonPath('data.items.0.item_details.nombre', 'Test Product')
->assertJsonPath('data.items.0.unit_price', '50.00')
->assertJsonPath('data.total', '100.00');
}
public function test_it_creates_a_direct_purchase_with_a_technical_checkout_cart(): void
@@ -165,7 +172,7 @@ class StorePurchaseTest extends TestCase
],
])
->assertCreated()
->assertJsonPath('data.items_source', 'cart')
->assertJsonPath('data.items_source', 'purchase')
->assertJsonPath('data.items.0.quantity', 3)
->assertJsonPath('data.total', '150.00');
@@ -174,8 +181,12 @@ class StorePurchaseTest extends TestCase
'status' => 'checkout',
'origin' => Cart::ORIGIN_DIRECT_CHECKOUT,
]);
$this->assertDatabaseMissing('compra_items', [
$this->assertDatabaseHas('compra_items', [
'compra_id' => $response->json('data.id'),
'source_variant_id' => $variant->id,
'cantidad' => 3,
'precio_unitario' => '50.00',
'total' => '150.00',
]);
$this->assertDatabaseHas('stock_reservations', [
'inventory_id' => $variant->inventory_id,
@@ -233,7 +244,7 @@ class StorePurchaseTest extends TestCase
],
])
->assertCreated()
->assertJsonPath('data.items_source', 'cart')
->assertJsonPath('data.items_source', 'purchase')
->assertJsonCount(2, 'data.items')
->assertJsonPath('data.items.0.source_variant_id', $firstVariant->id)
->assertJsonPath('data.items.1.source_variant_id', $secondVariant->id)
@@ -245,7 +256,7 @@ class StorePurchaseTest extends TestCase
'id' => $response->json('data.cart_id'),
'origin' => Cart::ORIGIN_DIRECT_CHECKOUT,
]);
$this->assertDatabaseMissing('compra_items', ['compra_id' => $purchaseId]);
$this->assertDatabaseCount('compra_items', 2);
$this->assertDatabaseHas('stock_reservations', [
'inventory_id' => $firstVariant->inventory_id,
'purchase_id' => $purchaseId,
@@ -541,7 +552,7 @@ class StorePurchaseTest extends TestCase
],
])
->assertCreated()
->assertJsonPath('data.items_source', 'cart')
->assertJsonPath('data.items_source', 'purchase')
->assertJsonCount(1, 'data.items')
->assertJsonPath('data.items.0.quantity', 2)
->assertJsonPath('data.dni', null)
@@ -599,373 +610,40 @@ class StorePurchaseTest extends TestCase
]);
}
public function test_it_updates_a_checkout_cart_item_quantity_and_its_stock_reservation(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['checkout_editing_policy' => 'full']);
$user = User::factory()->create();
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
$itemId = $purchase->cart->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
'cantidad' => 4,
])
->assertOk()
->assertJsonPath('data.items.0.cantidad', 4)
->assertJsonPath('data.subtotal', '200.00');
$this->assertDatabaseHas('inventories', [
'id' => $variant->inventory_id,
'reserved_stock' => 4,
]);
$this->assertDatabaseHas('carrito_items', [
'cart_id' => $purchase->cart_id,
'catalog_item_id' => $variant->catalog_item_id,
'variant_id' => $variant->id,
'cantidad' => 4,
]);
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
'cantidad' => 1,
])
->assertOk()
->assertJsonPath('data.items.0.cantidad', 1)
->assertJsonPath('data.subtotal', '50.00');
$this->assertDatabaseHas('compras', [
'id' => $purchase->id,
'total' => '50.00',
]);
$this->assertDatabaseHas('inventories', [
'id' => $variant->inventory_id,
'reserved_stock' => 1,
]);
}
public function test_checkout_cart_editing_uses_checkout_policy_independently_from_cart_policy(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update([
'cart_editing_policy' => 'full',
'checkout_editing_policy' => 'disabled',
]);
$user = User::factory()->create();
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
$itemId = $purchase->cart->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
'cantidad' => 4,
])
->assertUnprocessable()
->assertJsonValidationErrors('cantidad');
$this->assertDatabaseHas('carrito_items', [
'id' => $itemId,
'cantidad' => 2,
]);
}
public function test_it_removes_an_item_from_the_checkout_cart_when_the_policy_allows_it(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['checkout_editing_policy' => 'quantity_and_remove']);
$user = User::factory()->create();
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
$itemId = $purchase->cart->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->deleteJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}")
->assertOk()
->assertJsonPath('data.items', [])
->assertJsonPath('data.subtotal', '0.00');
$this->assertDatabaseMissing('carrito_items', ['id' => $itemId]);
$this->assertDatabaseHas('compras', [
'id' => $purchase->id,
'total' => '0.00',
]);
$this->assertDatabaseHas('inventories', [
'id' => $variant->inventory_id,
'reserved_stock' => 0,
]);
}
public function test_it_changes_a_checkout_item_variant_and_moves_its_reservation(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['checkout_editing_policy' => 'full']);
$user = User::factory()->create();
$firstVariant = $this->createVariantForTenant('sonder', 10, '50.00');
$secondInventory = Inventory::query()->create(['real_stock' => 10]);
$secondVariant = $firstVariant->catalogItem->variants()->create([
'inventory_id' => $secondInventory->id,
'precio' => '70.00',
]);
$purchase = $this->createCheckoutPurchase($user, 'sonder', $firstVariant, 2);
$itemId = $purchase->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$itemId}", [
'variant_id' => $secondVariant->id,
])
->assertOk()
->assertJsonPath('data.items.0.source_variant_id', $secondVariant->id)
->assertJsonPath('data.items.0.quantity', 2)
->assertJsonPath('data.items.0.unit_price', '70.00')
->assertJsonPath('data.items.0.line_total', '140.00')
->assertJsonPath('data.total', '140.00')
->assertJsonPath('data.items.0.variants.0.id', $firstVariant->id)
->assertJsonPath('data.items.0.variants.1.id', $secondVariant->id);
$this->assertDatabaseHas('inventories', [
'id' => $firstVariant->inventory_id,
'reserved_stock' => 0,
]);
$this->assertDatabaseHas('inventories', [
'id' => $secondInventory->id,
'reserved_stock' => 2,
]);
$this->assertDatabaseHas('carrito_items', [
'cart_id' => $purchase->cart_id,
'catalog_item_id' => $firstVariant->catalog_item_id,
'variant_id' => $secondVariant->id,
'cantidad' => 2,
]);
$this->assertDatabaseMissing('carrito_items', [
'cart_id' => $purchase->cart_id,
'variant_id' => $firstVariant->id,
]);
}
public function test_it_rejects_checkout_variant_changes_when_the_policy_does_not_allow_them(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['checkout_editing_policy' => 'quantity_and_remove']);
$user = User::factory()->create();
$firstVariant = $this->createVariantForTenant('sonder', 10, '50.00');
$secondInventory = Inventory::query()->create(['real_stock' => 10]);
$secondVariant = $firstVariant->catalogItem->variants()->create([
'inventory_id' => $secondInventory->id,
]);
$purchase = $this->createCheckoutPurchase($user, 'sonder', $firstVariant, 2);
$itemId = $purchase->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$itemId}", [
'variant_id' => $secondVariant->id,
])
->assertUnprocessable()
->assertJsonValidationErrors('variant_id');
$this->assertDatabaseHas('compra_items', [
'id' => $itemId,
'source_variant_id' => $firstVariant->id,
'cantidad' => 2,
]);
$this->assertDatabaseHas('inventories', [
'id' => $firstVariant->inventory_id,
'reserved_stock' => 2,
]);
$this->assertDatabaseHas('inventories', [
'id' => $secondInventory->id,
'reserved_stock' => 0,
]);
}
public function test_checkout_variant_change_rolls_back_when_the_target_has_insufficient_stock(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['checkout_editing_policy' => 'full']);
$user = User::factory()->create();
$firstVariant = $this->createVariantForTenant('sonder', 10, '50.00');
$secondInventory = Inventory::query()->create(['real_stock' => 1]);
$secondVariant = $firstVariant->catalogItem->variants()->create([
'inventory_id' => $secondInventory->id,
'precio' => '70.00',
]);
$purchase = $this->createCheckoutPurchase($user, 'sonder', $firstVariant, 2);
$itemId = $purchase->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$itemId}", [
'variant_id' => $secondVariant->id,
])
->assertUnprocessable()
->assertJsonValidationErrors('variant_id');
$this->assertDatabaseHas('compra_items', [
'id' => $itemId,
'source_variant_id' => $firstVariant->id,
'cantidad' => 2,
'precio_unitario' => '50.00',
]);
$this->assertDatabaseHas('inventories', [
'id' => $firstVariant->inventory_id,
'reserved_stock' => 2,
]);
$this->assertDatabaseHas('inventories', [
'id' => $secondInventory->id,
'reserved_stock' => 0,
]);
$this->assertDatabaseHas('carrito_items', [
'cart_id' => $purchase->cart_id,
'variant_id' => $firstVariant->id,
'cantidad' => 2,
]);
}
public function test_changing_to_an_existing_checkout_variant_merges_purchase_and_cart_rows(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['checkout_editing_policy' => 'full']);
$user = User::factory()->create();
$firstVariant = $this->createVariantForTenant('sonder', 10, '50.00');
$secondInventory = Inventory::query()->create(['real_stock' => 10]);
$secondVariant = $firstVariant->catalogItem->variants()->create([
'inventory_id' => $secondInventory->id,
'precio' => '70.00',
]);
$cart = Cart::query()->create([
'tenant_codigo' => 'sonder',
'user_id' => $user->id,
'status' => 'active',
]);
$cart->addItem($firstVariant->catalog_item_id, $firstVariant->id, 2);
$cart->addItem($secondVariant->catalog_item_id, $secondVariant->id, 3);
$purchase = app(CheckoutService::class)->startCheckout($tenant, $user->id, [
'cart_id' => $cart->id,
]);
$sourceItem = $purchase->items->firstWhere('source_variant_id', $firstVariant->id);
$this->assertNotNull($sourceItem);
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$sourceItem->id}", [
'variant_id' => $secondVariant->id,
])
->assertOk()
->assertJsonCount(1, 'data.items')
->assertJsonPath('data.items.0.source_variant_id', $secondVariant->id)
->assertJsonPath('data.items.0.quantity', 5)
->assertJsonPath('data.items.0.line_total', '350.00')
->assertJsonPath('data.total', '350.00');
$this->assertDatabaseCount('compra_items', 1);
$this->assertDatabaseHas('inventories', [
'id' => $firstVariant->inventory_id,
'reserved_stock' => 0,
]);
$this->assertDatabaseHas('inventories', [
'id' => $secondInventory->id,
'reserved_stock' => 5,
]);
$this->assertDatabaseCount('carrito_items', 1);
$this->assertDatabaseHas('carrito_items', [
'cart_id' => $cart->id,
'variant_id' => $secondVariant->id,
'cantidad' => 5,
]);
}
public function test_it_rejects_a_quantity_update_above_the_user_purchase_limit(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['checkout_editing_policy' => 'full']);
$user = User::factory()->create();
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
$variant->catalogItem->update(['max_units_per_user' => 3]);
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
$itemId = $purchase->cart->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
'cantidad' => 4,
])
->assertUnprocessable()
->assertJsonValidationErrors('cantidad');
$this->assertDatabaseHas('carrito_items', [
'id' => $itemId,
'cantidad' => 2,
]);
$this->assertDatabaseHas('inventories', [
'id' => $variant->inventory_id,
'reserved_stock' => 2,
]);
}
public function test_it_invalidates_a_pending_payment_when_the_checkout_cart_changes(): void
{
$tenant = $this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$tenant->update(['checkout_editing_policy' => 'full']);
$user = User::factory()->create();
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
$purchase->update([
'status' => Purchase::STATUS_PENDING_PAYMENT,
'payment_method' => 'qr',
]);
$purchase->telepagosQr()->create([
'qr_order_id' => 'stale-order',
'qr_code' => 'stale-qr',
]);
$itemId = $purchase->cart->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
'cantidad' => 3,
])
->assertOk()
->assertJsonPath('data.items.0.cantidad', 3);
$this->assertDatabaseHas('compras', [
'id' => $purchase->id,
'status' => Purchase::STATUS_CREATED,
'payment_method' => null,
'total' => '150.00',
]);
$this->assertDatabaseMissing('telepagos_qr', [
'compra_id' => $purchase->id,
'qr_order_id' => 'stale-order',
]);
}
public function test_a_no_op_checkout_cart_update_keeps_the_pending_payment_intact(): void
public function test_checkout_items_are_immutable_and_editing_routes_are_unavailable(): void
{
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$user = User::factory()->create();
$variant = $this->createVariantForTenant('sonder', 10, '50.00');
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
$purchase->update([
'status' => Purchase::STATUS_PENDING_PAYMENT,
'payment_method' => 'qr',
]);
$purchase->telepagosQr()->create([
'qr_order_id' => 'current-order',
'qr_code' => 'current-qr',
]);
$itemId = $purchase->cart->items->firstOrFail()->id;
$cartItemId = $purchase->cart->items->firstOrFail()->id;
$purchaseItemId = $purchase->items->firstOrFail()->id;
$this->actingAs($user, 'sanctum')
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$itemId}", [
'cantidad' => 2,
->patchJson("/api/tenants/sonder/checkout-carts/{$purchase->cart_id}/items/{$cartItemId}", [
'cantidad' => 3,
])
->assertOk();
->assertNotFound();
$this->assertDatabaseHas('compras', [
'id' => $purchase->id,
'status' => Purchase::STATUS_PENDING_PAYMENT,
'payment_method' => 'qr',
$this->patchJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$purchaseItemId}", [
'quantity' => 3,
])->assertNotFound();
$this->deleteJson("/api/tenants/sonder/compras/{$purchase->id}/items/{$purchaseItemId}")
->assertNotFound();
$this->postJson("/api/tenants/sonder/compras/{$purchase->id}/edit-items")
->assertNotFound();
$this->assertDatabaseHas('compra_items', [
'id' => $purchaseItemId,
'cantidad' => 2,
'precio_unitario' => '50.00',
'total' => '100.00',
]);
$this->assertDatabaseHas('telepagos_qr', [
'compra_id' => $purchase->id,
'qr_order_id' => 'current-order',
$this->assertDatabaseHas('inventories', [
'id' => $variant->inventory_id,
'reserved_stock' => 2,
]);
}
@@ -1129,7 +807,10 @@ class StorePurchaseTest extends TestCase
'id' => $purchase->id,
'status' => Purchase::STATUS_EXPIRED,
]);
$this->assertDatabaseMissing('compra_items', ['compra_id' => $purchase->id]);
$this->assertDatabaseHas('compra_items', [
'compra_id' => $purchase->id,
'cantidad' => 3,
]);
$this->assertDatabaseHas('stock_reservations', [
'purchase_id' => $purchase->id,
'quantity' => 0,
@@ -1153,7 +834,7 @@ class StorePurchaseTest extends TestCase
->assertSuccessful();
}
public function test_it_continues_expiring_purchases_after_an_inconsistent_reservation(): void
public function test_it_expires_multiple_purchases_that_already_have_item_snapshots(): void
{
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$user = User::factory()->create();
@@ -1176,13 +857,13 @@ class StorePurchaseTest extends TestCase
$this->travel(31)->minutes();
$this->artisan('reservations:expire')
->expectsOutput('Expired purchases: 1')
->expectsOutput('Expired purchases: 2')
->expectsOutput('Expired cart items: 0')
->assertSuccessful();
$this->assertDatabaseHas('compras', [
'id' => $inconsistentPurchase->id,
'status' => Purchase::STATUS_CREATED,
'status' => Purchase::STATUS_EXPIRED,
]);
$this->assertDatabaseHas('compras', [
'id' => $validPurchase->id,
@@ -1190,7 +871,7 @@ class StorePurchaseTest extends TestCase
]);
}
public function test_purchase_detail_uses_cart_items_for_created_purchase(): void
public function test_purchase_detail_uses_purchase_item_snapshot_for_created_purchase(): void
{
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$user = User::factory()->create([
@@ -1203,7 +884,7 @@ class StorePurchaseTest extends TestCase
->getJson("/api/tenants/sonder/compras/{$purchase->id}")
->assertOk()
->assertJsonPath('data.status', Purchase::STATUS_CREATED)
->assertJsonPath('data.items_source', 'cart')
->assertJsonPath('data.items_source', 'purchase')
->assertJsonCount(1, 'data.items')
->assertJsonPath('data.items.0.quantity', 2)
->assertJsonPath('data.items.0.unit_price', '50.00')
@@ -1242,7 +923,7 @@ class StorePurchaseTest extends TestCase
->assertJsonPath('data.items.0.item_details.imagen', null);
}
public function test_purchase_detail_uses_cart_items_for_pending_payment_purchase(): void
public function test_purchase_detail_uses_purchase_item_snapshot_for_pending_payment_purchase(): void
{
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$user = User::factory()->create([
@@ -1261,7 +942,7 @@ class StorePurchaseTest extends TestCase
->getJson("/api/tenants/sonder/compras/{$purchase->id}")
->assertOk()
->assertJsonPath('data.status', Purchase::STATUS_PENDING_PAYMENT)
->assertJsonPath('data.items_source', 'cart')
->assertJsonPath('data.items_source', 'purchase')
->assertJsonCount(1, 'data.items')
->assertJsonPath('data.items.0.quantity', 2)
->assertJsonPath('data.items.0.unit_price', '50.00')
@@ -1272,7 +953,7 @@ class StorePurchaseTest extends TestCase
->assertJsonPath('data.total', '100.00');
}
public function test_created_and_pending_payment_purchase_details_use_the_current_cart_quantity(): void
public function test_created_and_pending_payment_purchase_details_ignore_later_cart_changes(): void
{
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
$user = User::factory()->create([
@@ -1282,17 +963,6 @@ class StorePurchaseTest extends TestCase
foreach ([Purchase::STATUS_CREATED, Purchase::STATUS_PENDING_PAYMENT] as $status) {
$purchase = $this->createCheckoutPurchase($user, 'sonder', $variant, 2);
$purchase->items()->create([
'source_catalog_item_id' => $variant->catalog_item_id,
'source_variant_id' => $variant->id,
'item_nombre' => $variant->catalogItem->nombre,
'descripcion' => $variant->catalogItem->descripcion,
'slug' => $variant->catalogItem->slug,
'variant_attributes' => [],
'cantidad' => 1,
'precio_unitario' => '50.00',
'total' => '50.00',
]);
$purchase->cart->items()->update(['cantidad' => 3]);
$purchase->update(['status' => $status]);
@@ -1300,11 +970,11 @@ class StorePurchaseTest extends TestCase
->getJson("/api/tenants/sonder/compras/{$purchase->id}")
->assertOk()
->assertJsonPath('data.status', $status)
->assertJsonPath('data.items_source', 'cart')
->assertJsonPath('data.items.0.quantity', 3)
->assertJsonPath('data.items.0.line_total', '150.00')
->assertJsonPath('data.subtotal', '150.00')
->assertJsonPath('data.total', '150.00');
->assertJsonPath('data.items_source', 'purchase')
->assertJsonPath('data.items.0.quantity', 2)
->assertJsonPath('data.items.0.line_total', '100.00')
->assertJsonPath('data.subtotal', '100.00')
->assertJsonPath('data.total', '100.00');
}
}