176 lines
7.3 KiB
PHP
176 lines
7.3 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Cart;
|
|
|
|
use App\Domains\Cart\Models\Cart;
|
|
use App\Domains\Cart\Services\InvalidateEventDateCartsService;
|
|
use App\Domains\Catalog\Models\Inventory;
|
|
use App\Domains\Catalog\Models\StockReservation;
|
|
use App\Domains\Catalog\Services\StockReservationService;
|
|
use App\Domains\Tenant\Models\Tenant;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use Tests\TestCase;
|
|
|
|
class InvalidateEventDateCartsServiceTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Minimal service schema, still using the guarded SQLite :memory: connection.
|
|
Schema::create('carritos', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->string('tenant_codigo');
|
|
$table->string('status');
|
|
$table->unsignedBigInteger('current_purchase_id')->nullable();
|
|
$table->unsignedBigInteger('current_stock_reservation_id')->nullable();
|
|
$table->timestamps();
|
|
$table->softDeletes();
|
|
});
|
|
Schema::create('carrito_items', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->unsignedBigInteger('cart_id');
|
|
$table->unsignedBigInteger('variant_id');
|
|
});
|
|
Schema::create('variantes', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->unsignedBigInteger('event_date_id')->nullable();
|
|
$table->softDeletes();
|
|
});
|
|
Schema::create('event_dates', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->date('date')->nullable();
|
|
$table->time('time_start')->nullable();
|
|
});
|
|
Schema::create('variant_event_dates', function (Blueprint $table): void {
|
|
$table->unsignedBigInteger('variant_id');
|
|
$table->unsignedBigInteger('event_date_id');
|
|
});
|
|
Schema::create('compras', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->unsignedBigInteger('stock_reservation_id');
|
|
});
|
|
Schema::create('inventories', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->integer('real_stock')->default(10);
|
|
$table->integer('reserved_stock')->default(2);
|
|
$table->integer('sold_units')->default(0);
|
|
$table->integer('refunded_units')->default(0);
|
|
});
|
|
Schema::create('stock_reservations', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->string('status');
|
|
$table->timestamp('expires_at')->nullable();
|
|
$table->timestamp('released_at')->nullable();
|
|
$table->timestamp('expired_at')->nullable();
|
|
$table->string('release_reason')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
Schema::create('stock_reservation_lines', function (Blueprint $table): void {
|
|
$table->id();
|
|
$table->unsignedBigInteger('stock_reservation_id');
|
|
$table->unsignedBigInteger('inventory_id');
|
|
$table->integer('quantity');
|
|
});
|
|
DB::table('event_dates')->insert([['id' => 1], ['id' => 2]]);
|
|
DB::table('variantes')->insert([
|
|
['id' => 1, 'event_date_id' => 1],
|
|
['id' => 2, 'event_date_id' => 2],
|
|
['id' => 3, 'event_date_id' => null],
|
|
]);
|
|
DB::table('variant_event_dates')->insert(['variant_id' => 3, 'event_date_id' => 1]);
|
|
}
|
|
|
|
public static function releaseReasons(): array
|
|
{
|
|
return [
|
|
[StockReservationService::REASON_EVENT_DATE_RESCHEDULED],
|
|
[StockReservationService::REASON_EVENT_DATE_SUSPENDED],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('releaseReasons')]
|
|
public function test_invalidates_whole_cart_and_releases_all_its_stock_only_once(string $reason): void
|
|
{
|
|
$cart = $this->cart(1);
|
|
$otherInventory = DB::table('inventories')->insertGetId([]);
|
|
DB::table('carrito_items')->insert(['cart_id' => $cart->id, 'variant_id' => 2]);
|
|
DB::table('stock_reservation_lines')->insert([
|
|
'stock_reservation_id' => $cart->current_stock_reservation_id,
|
|
'inventory_id' => $otherInventory,
|
|
'quantity' => 2,
|
|
]);
|
|
$reservationId = $cart->current_stock_reservation_id;
|
|
$this->invalidate($reason);
|
|
$this->invalidate($reason);
|
|
|
|
$this->assertSame(Cart::STATUS_EXPIRED, $cart->fresh()->status);
|
|
$this->assertNull($cart->fresh()->current_stock_reservation_id);
|
|
$this->assertSame(0, (int) Inventory::query()->sum('reserved_stock'));
|
|
$this->assertSame(20, (int) Inventory::query()->sum('real_stock'));
|
|
$this->assertDatabaseHas('stock_reservations', [
|
|
'id' => $reservationId, 'status' => StockReservation::STATUS_RELEASED,
|
|
'release_reason' => $reason,
|
|
]);
|
|
}
|
|
|
|
public function test_preserves_other_dates_tenants_and_purchase_reservations(): void
|
|
{
|
|
$otherDate = $this->cart(2);
|
|
$otherTenant = $this->cart(1, 'other');
|
|
$purchaseCart = $this->cart(1);
|
|
DB::table('compras')->insert(['stock_reservation_id' => $purchaseCart->current_stock_reservation_id]);
|
|
$currentPurchaseCart = $this->cart(1);
|
|
$currentPurchaseCart->update(['current_purchase_id' => 99]);
|
|
$affected = $this->cart(3);
|
|
|
|
$this->invalidate();
|
|
|
|
foreach ([$otherDate, $otherTenant, $purchaseCart, $currentPurchaseCart] as $cart) {
|
|
$this->assertSame(Cart::STATUS_ACTIVE, $cart->fresh()->status);
|
|
$this->assertSame(StockReservation::STATUS_ACTIVE, $cart->fresh()->currentStockReservation->status);
|
|
}
|
|
$this->assertSame(Cart::STATUS_EXPIRED, $affected->fresh()->status);
|
|
$this->assertSame(8, (int) Inventory::query()->sum('reserved_stock'));
|
|
}
|
|
|
|
public function test_rolls_back_invalidation_when_the_date_change_fails(): void
|
|
{
|
|
$cart = $this->cart(1);
|
|
DB::beginTransaction();
|
|
$this->invalidate();
|
|
DB::rollBack();
|
|
|
|
$this->assertSame(Cart::STATUS_ACTIVE, $cart->fresh()->status);
|
|
$this->assertSame(StockReservation::STATUS_ACTIVE, $cart->fresh()->currentStockReservation->status);
|
|
$this->assertSame(2, (int) Inventory::query()->sum('reserved_stock'));
|
|
}
|
|
|
|
private function invalidate(string $reason = StockReservationService::REASON_EVENT_DATE_RESCHEDULED): void
|
|
{
|
|
app(InvalidateEventDateCartsService::class)->invalidate(new Tenant(['codigo' => 'acme']), collect([1]), $reason);
|
|
}
|
|
|
|
private function cart(int $variantId, string $tenantCode = 'acme'): Cart
|
|
{
|
|
$reservation = StockReservation::query()->create([
|
|
'status' => StockReservation::STATUS_ACTIVE, 'expires_at' => now()->addHour(),
|
|
]);
|
|
$cart = Cart::query()->create([
|
|
'tenant_codigo' => $tenantCode, 'status' => Cart::STATUS_ACTIVE,
|
|
'current_stock_reservation_id' => $reservation->id,
|
|
]);
|
|
DB::table('carrito_items')->insert(['cart_id' => $cart->id, 'variant_id' => $variantId]);
|
|
DB::table('stock_reservation_lines')->insert([
|
|
'stock_reservation_id' => $reservation->id,
|
|
'inventory_id' => DB::table('inventories')->insertGetId([]),
|
|
'quantity' => 2,
|
|
]);
|
|
|
|
return $cart;
|
|
}
|
|
}
|