- 83 Komponenten: Livewire\Volt\Component → Livewire\Component
- 73 Routen (admin, auth, customer): Volt::route() → Route::livewire()
- 88 Testdateien: Volt::test/actingAs → Livewire::test/actingAs
- VoltServiceProvider gelöscht; composer remove livewire/volt
- config/livewire.php auf v4-Schlüssel (component_layout, component_placeholder,
component_locations, make_command: SFC ohne ⚡). Der alte Schlüssel layout war
wirkungslos; alle gerouteten Komponenten setzen ihr Layout selbst.
- CLAUDE.md, AGENTS.md, boost.json, .cursorrules, Skill volt-development
- Roadmap: erledigt; Larastan erfasst SFCs weiterhin nicht (Blade-Dateien)
Suite 2.325 grün, einer übersprungen; Larastan und Pint ohne Meldung;
route:cache und view:cache laufen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
199 lines
6.2 KiB
PHP
199 lines
6.2 KiB
PHP
<?php
|
|
|
|
use App\Models\Plan;
|
|
use App\Models\User;
|
|
use App\Services\Billing\StripePlanSyncService;
|
|
use Database\Seeders\RolesAndPermissionsSeeder;
|
|
use Livewire\Livewire;
|
|
use Tests\TestCase;
|
|
|
|
beforeEach(function (): void {
|
|
/** @var TestCase $this */
|
|
$this->seed(RolesAndPermissionsSeeder::class);
|
|
});
|
|
|
|
function plansPageAdmin(): User
|
|
{
|
|
$admin = User::factory()->create(['is_active' => true]);
|
|
$admin->assignRole('admin');
|
|
|
|
return $admin;
|
|
}
|
|
|
|
test('the plans page lists active and inactive plans with prices', function () {
|
|
/** @var TestCase $this */
|
|
Plan::factory()->create([
|
|
'name' => 'Business',
|
|
'monthly_price_cents' => 4900,
|
|
'yearly_price_cents' => 49000,
|
|
'press_release_quota' => 10,
|
|
'daily_limit' => 2,
|
|
'stripe_product_id' => 'prod_test',
|
|
'stripe_price_id_monthly' => 'price_test_m',
|
|
'stripe_price_id_yearly' => 'price_test_y',
|
|
]);
|
|
Plan::factory()->inactive()->create(['name' => 'Altpaket']);
|
|
|
|
$this->actingAs(plansPageAdmin());
|
|
|
|
Livewire::test('admin.payments.plans')
|
|
->assertSee('Tarife & Pakete')
|
|
->assertSee('Business')
|
|
->assertSee('49,00 €')
|
|
->assertSee('490,00 €')
|
|
->assertSee('10 PM / Monat')
|
|
->assertSee('max. 2 / Tag')
|
|
->assertSee('verknüpft')
|
|
->assertSee('Altpaket')
|
|
->assertSee('Inaktiv');
|
|
});
|
|
|
|
test('a plan without stripe ids is marked as unsynced', function () {
|
|
/** @var TestCase $this */
|
|
Plan::factory()->create(['name' => 'Neu', 'stripe_product_id' => null]);
|
|
|
|
$this->actingAs(plansPageAdmin());
|
|
|
|
Livewire::test('admin.payments.plans')
|
|
->assertSee('nicht synchronisiert');
|
|
});
|
|
|
|
test('saving a plan updates the local record and triggers the stripe sync', function () {
|
|
/** @var TestCase $this */
|
|
$plan = Plan::factory()->create([
|
|
'name' => 'Business',
|
|
'monthly_price_cents' => 4900,
|
|
'yearly_price_cents' => 49000,
|
|
'press_release_quota' => 10,
|
|
'daily_limit' => null,
|
|
]);
|
|
|
|
$this->mock(StripePlanSyncService::class, function ($mock) use ($plan) {
|
|
$mock->shouldReceive('syncAfterUpdate')
|
|
->once()
|
|
->withArgs(function (Plan $synced, array $changes) use ($plan): bool {
|
|
return $synced->is($plan)
|
|
&& array_key_exists('monthly_price_cents', $changes)
|
|
&& array_key_exists('name', $changes);
|
|
});
|
|
});
|
|
|
|
$this->actingAs(plansPageAdmin());
|
|
|
|
Livewire::test('admin.payments.plans')
|
|
->call('edit', $plan->id)
|
|
->set('name', 'Business Plus')
|
|
->set('monthlyPrice', '59,00')
|
|
->set('yearlyPrice', '590')
|
|
->set('quota', '15')
|
|
->set('dailyLimit', '3')
|
|
->call('save')
|
|
->assertHasNoErrors()
|
|
->assertSee('Bestandsabos behalten ihren bisherigen Preis');
|
|
|
|
$plan->refresh();
|
|
|
|
expect($plan->name)->toBe('Business Plus')
|
|
->and($plan->monthly_price_cents)->toBe(5900)
|
|
->and($plan->yearly_price_cents)->toBe(59000)
|
|
->and($plan->press_release_quota)->toBe(15)
|
|
->and($plan->daily_limit)->toBe(3);
|
|
});
|
|
|
|
test('benefitsList returns a trimmed, non-empty list and tolerates null', function () {
|
|
$plan = Plan::factory()->create(['benefits' => [' Vorteil A ', '', 'Vorteil B', ' ']]);
|
|
|
|
expect($plan->benefitsList())->toBe(['Vorteil A', 'Vorteil B']);
|
|
|
|
$bare = Plan::factory()->create(['benefits' => null]);
|
|
expect($bare->benefitsList())->toBe([]);
|
|
});
|
|
|
|
test('saving a plan persists the benefits line by line', function () {
|
|
/** @var TestCase $this */
|
|
$plan = Plan::factory()->create(['name' => 'Business', 'benefits' => []]);
|
|
|
|
$this->mock(StripePlanSyncService::class, function ($mock) {
|
|
$mock->shouldReceive('syncAfterUpdate')->once();
|
|
});
|
|
|
|
$this->actingAs(plansPageAdmin());
|
|
|
|
Livewire::test('admin.payments.plans')
|
|
->call('edit', $plan->id)
|
|
->set('benefits', "Erster Vorteil\n\n Zweiter Vorteil \nDritter Vorteil")
|
|
->call('save')
|
|
->assertHasNoErrors();
|
|
|
|
expect($plan->refresh()->benefitsList())
|
|
->toBe(['Erster Vorteil', 'Zweiter Vorteil', 'Dritter Vorteil']);
|
|
});
|
|
|
|
test('editing a plan preloads its benefits as newline text', function () {
|
|
/** @var TestCase $this */
|
|
$plan = Plan::factory()->create(['benefits' => ['Vorteil eins', 'Vorteil zwei']]);
|
|
|
|
$this->actingAs(plansPageAdmin());
|
|
|
|
Livewire::test('admin.payments.plans')
|
|
->call('edit', $plan->id)
|
|
->assertSet('benefits', "Vorteil eins\nVorteil zwei");
|
|
});
|
|
|
|
test('saving without a price change shows the plain success message', function () {
|
|
/** @var TestCase $this */
|
|
$plan = Plan::factory()->create([
|
|
'name' => 'Starter',
|
|
'monthly_price_cents' => 2900,
|
|
'yearly_price_cents' => 29000,
|
|
'daily_limit' => 1,
|
|
]);
|
|
|
|
$this->mock(StripePlanSyncService::class, function ($mock) {
|
|
$mock->shouldReceive('syncAfterUpdate')->once();
|
|
});
|
|
|
|
$this->actingAs(plansPageAdmin());
|
|
|
|
Livewire::test('admin.payments.plans')
|
|
->call('edit', $plan->id)
|
|
->set('quota', '5')
|
|
->set('dailyLimit', '')
|
|
->call('save')
|
|
->assertHasNoErrors()
|
|
->assertSee('Tarif „Starter" gespeichert.')
|
|
->assertDontSee('Bestandsabos behalten');
|
|
|
|
$plan->refresh();
|
|
|
|
expect($plan->press_release_quota)->toBe(5)
|
|
->and($plan->daily_limit)->toBeNull();
|
|
});
|
|
|
|
test('invalid prices are rejected with validation errors', function () {
|
|
/** @var TestCase $this */
|
|
$plan = Plan::factory()->create();
|
|
|
|
$this->mock(StripePlanSyncService::class, function ($mock) {
|
|
$mock->shouldNotReceive('syncAfterUpdate');
|
|
});
|
|
|
|
$this->actingAs(plansPageAdmin());
|
|
|
|
Livewire::test('admin.payments.plans')
|
|
->call('edit', $plan->id)
|
|
->set('monthlyPrice', '-5')
|
|
->set('quota', 'abc')
|
|
->call('save')
|
|
->assertHasErrors(['monthlyPrice', 'quota']);
|
|
});
|
|
|
|
test('the plans page is not accessible for customers', function () {
|
|
/** @var TestCase $this */
|
|
$customer = User::factory()->create(['is_active' => true]);
|
|
$customer->assignRole('customer');
|
|
|
|
$this->actingAs($customer)
|
|
->get(route('admin.payments.plans'))
|
|
->assertForbidden();
|
|
});
|