presseportale/tests/Feature/CustomerStatusSummaryTest.php
Kevin Adametz 4ab746077c Status-Widget: immer verfuegbarer Buchungsueberblick in der Topbar
Ein Header-Dropdown oben rechts (Desktop) bzw. im Mobile-Header zeigt
fuer alle Kunden-Panel-User den aktuellen Status: aktuelle Buchung/Tarif,
verbleibendes PM-Kontingent und Credit-Guthaben. Ohne aktive Buchung
weist es direkt auf die noetige Buchung hin und verlinkt die Buchungsseite.

CustomerStatusSummary buendelt die Anzeige-Werte ueber die oeffentliche
User-API (Gating bleibt Single Source of Truth im Modell); die anonyme
Blade-Komponente x-customer.status-menu rendert das Dropdown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 13:03:25 +00:00

101 lines
3.3 KiB
PHP

<?php
use App\Enums\CreditTransactionType;
use App\Enums\Tier;
use App\Enums\UserPaymentOptionStatus;
use App\Models\User;
use App\Models\UserPaymentOption;
use App\Services\Billing\CreditWalletService;
use App\Services\Customer\CustomerStatusSummary;
use Database\Seeders\RolesAndPermissionsSeeder;
use Tests\TestCase;
beforeEach(function (): void {
/** @var TestCase $this */
$this->seed(RolesAndPermissionsSeeder::class);
});
test('a user without any booking and the gate off counts as unlimited and active', function () {
config()->set('billing.enforce_booking', false);
$user = User::factory()->create();
$status = CustomerStatusSummary::fromUser($user);
expect($status->tier)->toBe(Tier::Einzel);
expect($status->hasActiveBooking)->toBeTrue();
expect($status->unlimitedQuota)->toBeTrue();
expect($status->quotaRemaining)->toBeNull();
expect($status->bookingLabel())->toBe(__('Kein Abo'));
expect($status->badgeLabel())->toContain('∞');
});
test('a user without booking and the gate enforced has no active booking and zero quota', function () {
config()->set('billing.enforce_booking', true);
$user = User::factory()->create();
$status = CustomerStatusSummary::fromUser($user);
expect($status->hasActiveBooking)->toBeFalse();
expect($status->unlimitedQuota)->toBeFalse();
expect($status->quotaRemaining)->toBe(0);
expect($status->quotaTotal)->toBe(0);
});
test('a grandfathered bestandskunde is labelled and counts as active', function () {
config()->set('billing.enforce_booking', true);
$user = User::factory()->create();
UserPaymentOption::factory()->create([
'user_id' => $user->id,
'status' => UserPaymentOptionStatus::Grandfathered->value,
'stripe_subscription_id' => null,
]);
$status = CustomerStatusSummary::fromUser($user->fresh());
expect($status->hasActiveBooking)->toBeTrue();
expect($status->hasGrandfathered)->toBeTrue();
expect($status->bookingLabel())->toBe(__('Bestandstarif'));
});
test('credit balance is surfaced and shown in the badge', function () {
config()->set('billing.enforce_booking', true);
$user = User::factory()->create();
app(CreditWalletService::class)->credit($user, 27, CreditTransactionType::Topup, 'Paket 25');
$status = CustomerStatusSummary::fromUser($user->fresh());
expect($status->creditBalance)->toBe(27);
expect($status->hasCredits())->toBeTrue();
expect($status->badgeLabel())->toContain('27');
});
test('a user without credits hides the credit segment in the badge', function () {
config()->set('billing.enforce_booking', false);
$user = User::factory()->create();
$status = CustomerStatusSummary::fromUser($user);
expect($status->hasCredits())->toBeFalse();
expect($status->badgeLabel())->not->toContain('Cr');
});
test('the status menu renders inside the customer layout header', function () {
/** @var TestCase $this */
config()->set('billing.enforce_booking', true);
$user = User::factory()->create(['is_active' => true]);
$user->assignRole('customer');
$this->actingAs($user);
$this->get(route('me.dashboard'))
->assertOk()
->assertSee(__('Aktuelle Buchung'))
->assertSee(__('PMs frei'))
->assertSee(route('me.bookings.index'), false);
});