CluPilotCloud/tests/Feature/Billing/StoragePackLimitTest.php

123 lines
4.9 KiB
PHP

<?php
use App\Actions\BookAddon;
use App\Actions\OpenSubscription;
use App\Livewire\Billing;
use App\Livewire\ConfirmBookStorage;
use App\Models\Order;
use App\Models\Subscription;
use App\Models\User;
use App\Services\Billing\AddonCatalogue;
use Livewire\Livewire;
/**
* Höchstens drei Blöcke.
*
* Der Deckel liegt dort, wo Aufsteigen billiger wird: Start + 3 Blöcke sind
* 90 GB für 84 €, Team ist 85 GB für 79 € mit mehr Nutzern und mehr RAM. Er
* gehört in die Aktion und nicht ins Formular — dieselbe Lektion wie bei
* ReissueTakeover (v1.3.82): die Ansicht versteckte den Knopf, die Methode
* prüfte nichts, und Codex fand es zweimal.
*/
function limitContract(string $plan = 'start'): Subscription
{
$order = Order::factory()->create(['plan' => $plan, 'datacenter' => 'fsn', 'status' => 'paid']);
return app(OpenSubscription::class)($order);
}
/** The signed-in user behind a contract's customer, for the Livewire tests below. */
function limitUser(Subscription $subscription): User
{
return User::factory()->create(['email' => $subscription->customer->email]);
}
it('lehnt den vierten Block auf einmal ab', function () {
$subscription = limitContract();
expect(fn () => app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 4))
->toThrow(RuntimeException::class);
expect($subscription->addons()->count())->toBe(0);
});
it('zählt drei einzelne Buchungen als drei', function () {
$subscription = limitContract();
foreach (range(1, 3) as $ignored) {
app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 1, Order::factory()->create([
'customer_id' => $subscription->customer_id, 'type' => 'addon', 'addon_key' => 'storage', 'status' => 'paid',
]));
}
expect(fn () => app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 1, Order::factory()->create([
'customer_id' => $subscription->customer_id, 'type' => 'addon', 'addon_key' => 'storage', 'status' => 'paid',
])))->toThrow(RuntimeException::class);
expect((int) $subscription->addons()->active()->sum('quantity'))->toBe(3);
});
it('zählt eine gekündigte Buchung nicht mit', function () {
$subscription = limitContract();
$addon = app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 3);
app(BookAddon::class)->cancel($addon);
expect(app(AddonCatalogue::class)->bookableQuantity($subscription->refresh(), AddonCatalogue::STORAGE))->toBe(3);
});
it('sagt, wie viele noch gehen', function () {
$subscription = limitContract();
app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 2);
expect(app(AddonCatalogue::class)->bookableQuantity($subscription->refresh(), AddonCatalogue::STORAGE))->toBe(1);
});
/**
* Nachtrag aus der Durchsicht: der Deckel hielt in BookAddon, aber der
* Warenkorb kannte ihn nur halb.
*
* `purchase()` klemmte $lines mit `max(1, min(..., bookableQuantity, ...))` —
* bei erreichtem Deckel (bookableQuantity = 0) floort das `max(1, 0)` auf
* genau eine Bestellzeile statt auf keine. Der Knopf, der `purchase('storage')`
* ohne Menge aufruft, ist in der Ansicht bedingungslos gerendert, also
* entstand am Deckel weiterhin ein bezahlbarer Auftrag, den BookAddon nach
* der Zahlung nur noch ablehnen konnte — während OrderObserver den
* Fehlschlag abfängt und der Auftrag `paid` stehen bleibt. Genau das Szenario,
* dessentwegen diese Aufgabe existiert, nur auf den Fall "genau am Deckel"
* verengt statt beseitigt.
*/
it('legt am erreichten Deckel keine Bestellzeile mehr an', function () {
$subscription = limitContract();
app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 3);
withStripeSecret();
Livewire::actingAs(limitUser($subscription))
->test(Billing::class)
->call('purchase', 'storage');
expect(Order::where('customer_id', $subscription->customer_id)->where('type', 'storage')->count())->toBe(0);
});
/**
* Nachtrag aus der Durchsicht: das Bestätigungs-Modal klemmte an der
* absoluten Grenze (3), nicht an der Restmenge. Die Karte, die das Modal
* öffnet, kennt nur die Zahl, die einen Downgrade-Blocker aufheben würde —
* nicht, dass der Vertrag schon einen Block hält. Ohne diese Klemmung hätte
* der Dialog "3 Pakete" versprochen, während purchase() danach nur die
* tatsächlich noch buchbaren 2 in den Warenkorb gelegt hätte: Dialogtext und
* wirkliche Bestellmenge liefen auseinander.
*/
it('klemmt das Bestätigungs-Modal an der Restmenge, nicht an der absoluten Grenze', function () {
$subscription = limitContract();
app(BookAddon::class)($subscription, AddonCatalogue::STORAGE, 1);
Livewire::actingAs(limitUser($subscription))
->test(ConfirmBookStorage::class, ['packs' => 3])
->assertSet('packs', 2)
->assertSee(__('billing.storage_confirm_title', ['count' => 2]))
->call('proceed')
->assertDispatched('storage-packs-confirmed', packs: 2);
});