presseportale/tests/Feature/Customer/DashboardTest.php
Kevin Adametz c7bd940f28 Optimierung Phase 0: Korrektheit & Security
Konsolidierter Entwicklungsplan aus den zwei Analyse-Dokumenten plus
Umsetzung der Phase-0-Massnahmen (Korrektheit/Security, je mit Tests):

0.1 BasicAuth: timing-sicherer Vergleich (hash_equals, beide Felder ohne
    Short-Circuit); Enabled-Check zur Laufzeit via config('auth.basic.*')
    statt env() im Bootstrap-Closure (greift jetzt unter config:cache);
    Config-Block + .env.example. Fund: auth.basic.* war nie als Config
    definiert, Gate war faktisch unkonfigurierbar.
0.2 Legacy-Invoice-"latest"-Abfrage zentralisiert (LegacyInvoice::
    scopeLatestFirst + User::latestLegacyInvoice); beide Consumer N+1-frei
    umgestellt; unterschiedliches null-Verhalten bewusst erhalten.
0.3 Payment-Option-Eligibility vereinheitlicht (period-aware, strikt):
    UserPaymentOption::scopeActiveNow/grandfatheredNow + User::
    hasActivePaymentOption; hasActiveBooking/hasUnlimitedPressReleaseQuota
    und ApiAccessEligibilityService darauf umgestellt; $hasGrandfathered-
    Mislabel behoben.
0.4 Theme->Portal-Vokabular: totes, projektfremdes ThemeMiddleware
    geloescht; Mapping in Portal::fromTheme() zentralisiert; fremden
    b2in-Default im ThemeServiceProvider auf Portal-Default korrigiert.
0.5 InvoiceNumberGenerator: Erst-Insert race-sicher via insertOrIgnore
    (Unique-Index auf circle existiert bereits). CreditWalletService war
    bereits abgesichert (Unique-Index + createOrFirst).

Tests: 4 neue Dateien, 5 bestehende auf gueltiges grandfathered_until
angepasst. Volle Suite gruen (793 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:09:02 +00:00

205 lines
6.6 KiB
PHP

<?php
use App\Enums\PressReleaseStatus;
use App\Enums\UserPaymentOptionStatus;
use App\Models\BillingAddress;
use App\Models\Company;
use App\Models\PressRelease;
use App\Models\Profile;
use App\Models\User;
use App\Models\UserPaymentOption;
use Database\Seeders\RolesAndPermissionsSeeder;
use Livewire\Volt\Volt as LivewireVolt;
use Tests\TestCase;
beforeEach(function (): void {
/** @var TestCase $this */
$this->seed(RolesAndPermissionsSeeder::class);
});
test('customer dashboard renders core sections without errors', function () {
/** @var TestCase $this */
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
$this->actingAs($customer);
LivewireVolt::test('customer.dashboard')
->assertSee('Mein Dashboard')
->assertSee(__('Gesamt'))
->assertSee(__('Veröffentlicht'))
->assertSee(__('In Prüfung'))
->assertSee(__('Entwürfe'))
->assertSee(__('Meine letzten Pressemitteilungen'))
->assertSee(__('Datenqualität'))
->assertSee(__('Brand-Bridge'));
});
test('empty-state with three-step intro shows when no press releases exist', function () {
/** @var TestCase $this */
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
$this->actingAs($customer);
LivewireVolt::test('customer.dashboard')
->assertSee(__('Noch keine Pressemitteilungen'))
->assertSee(__('Erste Pressemitteilung erstellen'))
->assertSee(__('Firma zuordnen'))
->assertSee(__('Mitteilung verfassen'))
->assertSee(__('Zur Prüfung senden'));
});
test('press release list and KPI counts render when data exists', function () {
/** @var TestCase $this */
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
$company = Company::factory()->create(['owner_user_id' => $customer->id]);
PressRelease::factory()
->published()
->create([
'user_id' => $customer->id,
'company_id' => $company->id,
'title' => 'Phase 2 Demo Release',
]);
PressRelease::factory()->create([
'user_id' => $customer->id,
'company_id' => $company->id,
'status' => PressReleaseStatus::Draft->value,
]);
$this->actingAs($customer);
LivewireVolt::test('customer.dashboard')
->assertSee('Phase 2 Demo Release')
->assertDontSee(__('Noch keine Pressemitteilungen'))
->assertSee('Alle anzeigen');
});
test('company dashboard preview is limited to the ten newest companies', function () {
/** @var TestCase $this */
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
for ($i = 1; $i <= 12; $i++) {
Company::factory()->create([
'owner_user_id' => $customer->id,
'name' => sprintf('Dashboard Firma %02d', $i),
'created_at' => now()->subDays(13 - $i),
]);
}
$this->actingAs($customer);
LivewireVolt::test('customer.dashboard')
->assertSee('12 zugeordnet')
->assertSee('Dashboard Firma 12')
->assertSee('Dashboard Firma 03')
->assertDontSee('Dashboard Firma 02')
->assertDontSee('Dashboard Firma 01')
->assertSee('Die zehn neuesten Firmen werden hier als Vorschau angezeigt.')
->assertSee(route('me.press-kits.index'), false);
});
test('profile completeness hint with percentage appears for partial profiles', function () {
/** @var TestCase $this */
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
Profile::factory()->create([
'user_id' => $customer->id,
'first_name' => 'Test',
'last_name' => 'Customer',
'phone' => null,
'address' => null,
'country_code' => null,
'salutation_key' => null,
]);
$this->actingAs($customer);
LivewireVolt::test('customer.dashboard')
->assertSee(__('Profil unvollständig'))
->assertSee('33');
});
test('the booking-required banner stays hidden while the gate is open', function () {
/** @var TestCase $this */
config()->set('billing.enforce_booking', false);
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
$this->actingAs($customer);
LivewireVolt::test('customer.dashboard')
->assertDontSee(__('Buchung erforderlich, um zu veröffentlichen'));
});
test('the booking-required banner appears when the gate is enforced and no booking exists', function () {
/** @var TestCase $this */
config()->set('billing.enforce_booking', true);
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
$this->actingAs($customer);
LivewireVolt::test('customer.dashboard')
->assertSee(__('Buchung erforderlich, um zu veröffentlichen'))
->assertSee(route('me.bookings.index'), false);
});
test('a grandfathered bestandskunde does not see the booking-required banner', function () {
/** @var TestCase $this */
config()->set('billing.enforce_booking', true);
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
UserPaymentOption::factory()->create([
'user_id' => $customer->id,
'status' => UserPaymentOptionStatus::Grandfathered->value,
'grandfathered_until' => now()->addYear(),
'stripe_subscription_id' => null,
]);
$this->actingAs($customer);
LivewireVolt::test('customer.dashboard')
->assertDontSee(__('Buchung erforderlich, um zu veröffentlichen'));
});
test('billing address hint disappears once address is complete', function () {
/** @var TestCase $this */
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
Profile::factory()->create([
'user_id' => $customer->id,
'salutation_key' => 'mr',
'first_name' => 'Test',
'last_name' => 'Customer',
'phone' => '+49 30 1234567',
'address' => 'Teststr. 1',
'country_code' => 'DE',
]);
BillingAddress::query()->create([
'user_id' => $customer->id,
'name' => 'Test Customer GmbH',
'address1' => 'Teststr. 1',
'postal_code' => '10115',
'city' => 'Berlin',
'country_code' => 'DE',
]);
$this->actingAs($customer);
LivewireVolt::test('customer.dashboard')
->assertDontSee(__('Profil unvollständig'))
->assertDontSee(__('Rechnungsadresse fehlt'))
->assertSee(__('Alles im grünen Bereich'));
});