presseportale/tests/Feature/Admin/AdminGrantTest.php
Kevin Adametz 2cde7284a1 livewire: Volt entfernt — Komponenten als Livewire-4-Single-File-Components, Route::livewire(), Livewire::test(); livewire/volt deinstalliert
- 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>
2026-10-06 13:59:36 +00:00

126 lines
5.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Enums\ActivityType;
use App\Enums\CreditTransactionType;
use App\Enums\SinglePurchaseStatus;
use App\Enums\SinglePurchaseType;
use App\Models\ActivityEvent;
use App\Models\User;
use App\Services\Billing\AdminGrantService;
use App\Services\Billing\CreditWalletService;
use Database\Seeders\RolesAndPermissionsSeeder;
use Livewire\Livewire;
use Spatie\Permission\PermissionRegistrar;
use Tests\TestCase;
/**
* Admin-Gutschriften ohne Zahlung (Admin ▸ Benutzer ▸ Guthaben & Kontingent):
* Credits als Grant ins Wallet-Ledger, freie PMs als 0-€-Einzelkäufe —
* beides protokolliert im Aktivitäts-Verlauf.
*/
test('an admin can grant credits without a payment', function () {
$admin = User::factory()->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.
Livewire::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 = Livewire::test('admin.users.show', ['id' => $admin->id]);
$releases = Livewire::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();
});