presseportale/tests/Feature/PressReleaseShowAddonsTest.php
Kevin Adametz 47c1cac5f5 PM-Liste: Hinweis auf laufende Boost-Platzierung unter dem Status
Badge "Boost bis TT.MM.JJJJ" in der Status-Spalte; Boost-Enden der
Seite in einer Query aggregiert statt isBoosted() je Zeile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:11:38 +00:00

229 lines
8 KiB
PHP

<?php
use App\Enums\PressReleaseClassification;
use App\Models\Boost;
use App\Models\Category;
use App\Models\Company;
use App\Models\PressRelease;
use App\Models\User;
use App\Services\Billing\CreditWalletService;
use App\Services\Billing\ProofPdfService;
use Database\Seeders\RolesAndPermissionsSeeder;
use Livewire\Volt\Volt as LivewireVolt;
use Tests\TestCase;
beforeEach(function (): void {
/** @var TestCase $this */
$this->seed(RolesAndPermissionsSeeder::class);
$this->wallet = app(CreditWalletService::class);
});
/**
* @return array{customer: User, pr: PressRelease}
*/
function publishedGreenPrForOwner(): array
{
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
$company = Company::factory()->presseecho()->create();
$customer->companies()->attach($company->id, ['role' => 'owner']);
$pr = PressRelease::factory()->published()->create([
'user_id' => $customer->id,
'company_id' => $company->id,
'category_id' => Category::factory()->create()->id,
'portal' => $company->portal->value,
'classification' => PressReleaseClassification::Green->value,
]);
return compact('customer', 'pr');
}
test('a published green release shows the boost and proof add-ons', function () {
/** @var TestCase $this */
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
$this->wallet->credit($customer, 40);
$this->actingAs($customer);
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
->assertSee('Add-ons')
->assertSee('Boost — Platzierung')
->assertSee('7 Tage für 12 Credits buchen')
->assertSee('Veröffentlichungsnachweis')
->assertSee('Für 3 Credits kaufen');
});
test('add-on buttons only debit after the purchase confirmation modal', function () {
/** @var TestCase $this */
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
$this->wallet->credit($customer, 40);
$this->actingAs($customer);
$component = LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
->call('confirmBoost', 14)
->assertSet('pendingPurchase', [
'type' => 'boost',
'label' => 'Boost — 14 Tage Platzierung',
'credits' => 20,
'days' => 14,
])
->assertSee('Kauf bestätigen')
->assertSee('Kostenpflichtig buchen');
// Der Klick auf die Laufzeit selbst bucht noch nichts ab.
expect($customer->fresh()->creditBalance())->toBe(40);
expect($pr->fresh()->isBoosted())->toBeFalse();
$component->call('buyPendingAddOn')
->assertSet('pendingPurchase', null);
expect($customer->fresh()->creditBalance())->toBe(20);
expect($pr->fresh()->isBoosted())->toBeTrue();
});
test('the proof purchase runs through the confirmation modal as well', function () {
/** @var TestCase $this */
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
$this->wallet->credit($customer, 10);
$this->actingAs($customer);
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
->call('confirmProof')
->assertSet('pendingPurchase.type', 'proof')
->assertSet('pendingPurchase.credits', 3)
->call('buyPendingAddOn')
->assertSee('Nachweis herunterladen');
expect(app(ProofPdfService::class)->hasPurchased($customer->fresh(), $pr))->toBeTrue();
expect($customer->fresh()->creditBalance())->toBe(7);
});
test('an unknown boost duration does not open the confirmation modal', function () {
/** @var TestCase $this */
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
$this->wallet->credit($customer, 40);
$this->actingAs($customer);
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
->call('confirmBoost', 999)
->assertSet('pendingPurchase', null)
->call('buyPendingAddOn');
expect($customer->fresh()->creditBalance())->toBe(40); // nichts abgebucht
});
test('booking a boost from the detail page debits the wallet and activates it', function () {
/** @var TestCase $this */
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
$this->wallet->credit($customer, 40);
$this->actingAs($customer);
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
->call('buyBoost', 14);
expect($customer->fresh()->creditBalance())->toBe(20); // 40 - 20 (14 Tage)
expect($pr->fresh()->isBoosted())->toBeTrue();
});
test('buying a proof from the detail page unlocks the download', function () {
/** @var TestCase $this */
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
$this->wallet->credit($customer, 10);
$this->actingAs($customer);
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
->call('buyProof')
->assertSee('Nachweis herunterladen');
expect(app(ProofPdfService::class)->hasPurchased($customer->fresh(), $pr))->toBeTrue();
expect($customer->fresh()->creditBalance())->toBe(7);
});
test('an add-on purchase without enough credits opens the shortfall modal with a topup link', function () {
/** @var TestCase $this */
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
$this->wallet->credit($customer, 1);
$this->actingAs($customer);
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
->call('buyProof')
->assertSet('creditShortfall', [
'label' => 'Der Veröffentlichungsnachweis',
'required' => 3,
'available' => 1,
])
->assertSee('Nicht genügend Credits')
->assertSee('Es fehlen 2 Credits.')
->assertSee('Credits aufladen')
->assertSee(route('me.bookings.index').'#credits', escape: false);
expect($customer->fresh()->creditBalance())->toBe(1); // nichts abgebucht
});
test('a boost without enough credits opens the shortfall modal instead of booking', function () {
/** @var TestCase $this */
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
$this->actingAs($customer);
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
->call('buyBoost', 7)
->assertSet('creditShortfall.label', 'Der Boost')
->assertSee('Nicht genügend Credits');
expect($pr->fresh()->isBoosted())->toBeFalse();
});
test('the press release list shows a hint with end date for an active boost', function () {
/** @var TestCase $this */
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
$this->wallet->credit($customer, 40);
$this->actingAs($customer);
LivewireVolt::test('customer.press-releases.index')
->assertDontSee('Boost bis');
Boost::factory()->for($pr)->create([
'ends_at' => now()->addDays(7)->setTime(9, 0),
]);
LivewireVolt::test('customer.press-releases.index')
->assertSee('Boost bis '.now()->addDays(7)->format('d.m.Y'));
});
test('an expired boost shows no hint in the press release list', function () {
/** @var TestCase $this */
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
$this->actingAs($customer);
Boost::factory()->for($pr)->expired()->create();
LivewireVolt::test('customer.press-releases.index')
->assertDontSee('Boost bis');
});
test('a non-green release cannot be boosted from the detail page', function () {
/** @var TestCase $this */
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
$pr->update(['classification' => PressReleaseClassification::Yellow->value]);
$this->wallet->credit($customer, 40);
$this->actingAs($customer);
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
->assertSee('Nur grün geprüfte Pressemitteilungen sind boostbar')
->call('buyBoost', 7);
expect($customer->fresh()->creditBalance())->toBe(40); // nichts abgebucht
expect($pr->fresh()->isBoosted())->toBeFalse();
});