create(['name' => 'Admin Ada']); $user = User::factory()->create(); app(AdminGrantService::class)->grantCredits($user, 500, $admin, 'Interne Nutzung'); expect(app(CreditWalletService::class)->balance($user))->toBe(500); $transaction = $user->creditWallet()->sole()->transactions()->sole(); expect($transaction->type)->toBe(CreditTransactionType::Grant) ->and($transaction->amount_credits)->toBe(500) ->and($transaction->description)->toContain('Admin Ada') ->and($transaction->description)->toContain('Interne Nutzung'); $event = ActivityEvent::query()->where('type', ActivityType::AdminGrant->value)->sole(); expect($event->user_id)->toBe($user->id) ->and($event->description)->toContain('500 Credits') ->and($event->description)->toContain('ohne Zahlung') ->and($event->meta['granted_by'])->toBe($admin->id); }); test('an admin can grant free press releases that satisfy the booking gate', function () { config(['billing.enforce_booking' => true]); $admin = User::factory()->create(); $user = User::factory()->create(); expect($user->hasActiveBooking())->toBeFalse(); app(AdminGrantService::class)->grantFreePressReleases($user, 3, $admin, 'Eigene PMs'); $purchases = $user->singlePurchases()->get(); expect($purchases)->toHaveCount(3) ->and($purchases->first()->type)->toBe(SinglePurchaseType::SinglePm) ->and($purchases->first()->status)->toBe(SinglePurchaseStatus::Paid) ->and($purchases->first()->price_cents)->toBe(0) ->and($purchases->first()->paid_at)->not->toBeNull(); $fresh = User::query()->find($user->id); expect($fresh->hasActiveBooking())->toBeTrue() ->and($fresh->openSinglePurchaseCount())->toBe(3) ->and($fresh->pressReleaseQuotaRemaining())->toBe(3); $event = ActivityEvent::query()->where('type', ActivityType::AdminGrant->value)->sole(); expect($event->description)->toContain('3 freie Pressemitteilung(en)') ->and($event->meta['free_press_releases'])->toBe(3); }); test('the user show page grants credits and free pms through the panel', function () { /** @var TestCase $this */ $this->seed(RolesAndPermissionsSeeder::class); $admin = User::factory()->create(['is_active' => true]); $admin->assignRole('admin'); $this->actingAs($admin); // Gutschrift auf das eigene Admin-Konto — der Live-Anwendungsfall. LivewireVolt::test('admin.users.show', ['id' => $admin->id]) ->set('grantCreditsAmount', 250) ->set('grantCreditsNote', 'Eigenbedarf') ->call('grantCredits') ->assertHasNoErrors() ->set('grantPmCount', 5) ->call('grantFreePressReleases') ->assertHasNoErrors(); expect(app(CreditWalletService::class)->balance($admin))->toBe(250) ->and($admin->singlePurchases()->grantingSubmission()->count())->toBe(5); }); test('the grant panel is not available without the users manage permission', function () { /** @var TestCase $this */ $this->seed(RolesAndPermissionsSeeder::class); $editor = User::factory()->create(['is_active' => true]); $editor->assignRole('editor'); $this->actingAs($editor); $this->get(route('admin.users.show', $editor->id))->assertForbidden(); }); test('grants are refused once the permission is revoked while the panel stays open (Audit F08)', function () { /** @var TestCase $this */ $this->seed(RolesAndPermissionsSeeder::class); $admin = User::factory()->create(['is_active' => true]); $admin->assignRole('admin'); $this->actingAs($admin); // Zwei geladene Komponenten (ein 403 verwirft den Livewire-Snapshot). $credits = LivewireVolt::test('admin.users.show', ['id' => $admin->id]); $releases = LivewireVolt::test('admin.users.show', ['id' => $admin->id]); // Herabstufung, während die Komponenten bereits geladen sind. $admin->syncRoles(['editor']); app()[PermissionRegistrar::class]->forgetCachedPermissions(); $credits->set('grantCreditsAmount', 250)->call('grantCredits')->assertForbidden(); $releases->set('grantPmCount', 2)->call('grantFreePressReleases')->assertForbidden(); expect(app(CreditWalletService::class)->balance($admin))->toBe(0) ->and($admin->singlePurchases()->count())->toBe(0); // Die Route selbst verlangt die Berechtigung – nicht nur mount(). $this->get(route('admin.users.show', $admin->id))->assertForbidden(); });