123 lines
4.9 KiB
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);
|
|
});
|