'Reservable', 'sku' => 'RES-'.uniqid(), 'type' => ProductType::SIMPLE, 'status' => ProductStatus::PUBLISHED, 'is_visible' => true, 'manage_stock' => true, ]); } #[Test] public function returns_zero_count_when_nothing_has_expired(): void { $product = $this->product(); $product->increaseStock(5); // An active, unexpired claim that should NOT be released. $product->claimStock( 1, null, Carbon::now()->subMinutes(5), Carbon::now()->addHours(2), 'active claim', ); $exit = Artisan::call(ReleaseExpiredStocks::class); $output = Artisan::output(); $this->assertSame(0, $exit); $this->assertStringContainsString('Released 0 expired stock claim(s).', $output); } #[Test] public function releases_only_expired_claims_and_reports_the_count(): void { Carbon::setTestNow(Carbon::parse('2026-05-14 10:00:00')); $product = $this->product(); $product->increaseStock(3); // Expired claim — claim window ended an hour ago. $product->claimStock( 1, null, Carbon::parse('2026-05-14 08:00:00'), Carbon::parse('2026-05-14 09:00:00'), 'expired claim', ); // Active claim — still open. $product->claimStock( 1, null, Carbon::parse('2026-05-14 09:30:00'), Carbon::parse('2026-05-14 18:00:00'), 'active claim', ); $exit = Artisan::call(ReleaseExpiredStocks::class); $output = Artisan::output(); $this->assertSame(0, $exit); $this->assertStringContainsString('Released 1 expired stock claim(s).', $output); // The expired claim flipped to COMPLETED status (released), the active // one still has PENDING. $pendingClaims = $product->stocks() ->where('type', StockType::CLAIMED->value) ->where('status', StockStatus::PENDING->value) ->get(); $this->assertCount(1, $pendingClaims); $this->assertSame('active claim', $pendingClaims->first()->note); } #[Test] public function short_circuits_when_auto_release_is_disabled_in_config(): void { config(['shop.stock.auto_release_expired' => false]); $product = $this->product(); $product->increaseStock(2); Carbon::setTestNow(Carbon::parse('2026-05-14 10:00:00')); $product->claimStock( 1, null, Carbon::parse('2026-05-14 08:00:00'), Carbon::parse('2026-05-14 09:00:00'), 'would-be-released', ); $exit = Artisan::call(ReleaseExpiredStocks::class); $output = Artisan::output(); $this->assertSame(0, $exit); $this->assertStringContainsString('Auto-release is disabled in config.', $output); // The claim is still PENDING since the command bailed. $stillPending = $product->stocks() ->where('type', StockType::CLAIMED->value) ->where('status', StockStatus::PENDING->value) ->count(); $this->assertSame(1, $stillPending); } }