presseportale/tests/Feature/Admin/AdminGrantTest.php
Kevin Adametz 3f4679fbb2 admin: Credits und freie PMs ohne Zahlung gutschreiben
Neues Panel "Guthaben & Kontingent" auf der Benutzer-Detailseite
(users:manage): Credits gehen als Grant-Buchung ins Wallet-Ledger,
freie Pressemitteilungen als bezahlte 0-€-Einzelkäufe, die das
Buchungs-Gate erfüllen und beim Veröffentlichen regulär eingelöst
werden — Stripe bleibt unberührt. Jede Gutschrift landet mit Vergeber
und Grund als neuer Typ "Gutschrift" im Aktivitäts-Verlauf.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:06:01 +00:00

99 lines
3.9 KiB
PHP

<?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\Volt\Volt as LivewireVolt;
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.
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();
});