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()); LivewireVolt::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()); LivewireVolt::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()); LivewireVolt::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('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()); LivewireVolt::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()); LivewireVolt::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(); });