create(['portal' => Portal::Presseecho]); $category->translations()->create([ 'locale' => 'de', 'name' => 'Energie & Klima', 'slug' => 'energie-klima', ]); $company = Company::factory()->presseecho()->create(['name' => 'Boost Energie GmbH']); $newest = PressRelease::factory() ->published() ->forPortal(Portal::Presseecho) ->for($category) ->for($company) ->create([ 'title' => 'Neueste unbezahlte Meldung', 'slug' => 'neueste-unbezahlte-meldung', 'published_at' => now()->subHour(), ]); $boosted = PressRelease::factory() ->published() ->forPortal(Portal::Presseecho) ->for($category) ->for($company) ->create([ 'title' => 'Geboostete aeltere Meldung', 'slug' => 'geboostete-aeltere-meldung', 'published_at' => now()->subDays(5), // Grün klassifiziert: Voraussetzung fürs Boost-Gate im BoostService. 'classification' => PressReleaseClassification::Green->value, ]); return compact('category', 'company', 'newest', 'boosted'); } test('a boosted release takes the homepage lead ahead of newer releases and is labelled', function () { /** @var TestCase $this */ ['boosted' => $boosted, 'newest' => $newest] = boostPlacementFixture(); Boost::factory()->for($boosted)->create(); $data = app(HomePageData::class)->build(Portal::Presseecho); expect($data['leadRelease']->id)->toBe($boosted->id) ->and($data['boostedIds'])->toBe([$boosted->id]) // Die verdrängte neueste Meldung rückt in die Seitenleiste nach. ->and($data['sideReleases']->modelKeys())->toContain($newest->id); // Badge „Top-Platzierung" hängt am Hero-Lead (nach dem Ticker-Vorkommen). $this->get('https://presseecho.test/de') ->assertSuccessful() ->assertSeeTextInOrder(['Top-Platzierung', 'Geboostete aeltere Meldung']); }); test('an expired boost does not influence the homepage placement', function () { /** @var TestCase $this */ ['boosted' => $boosted, 'newest' => $newest] = boostPlacementFixture(); Boost::factory()->for($boosted)->expired()->create(); $data = app(HomePageData::class)->build(Portal::Presseecho); expect($data['leadRelease']->id)->toBe($newest->id) ->and($data['boostedIds'])->toBe([]); $this->get('https://presseecho.test/de') ->assertSuccessful() ->assertDontSeeText('Top-Platzierung'); }); test('booking a boost invalidates the cached start and category pages immediately', function () { /** @var TestCase $this */ ['boosted' => $boosted, 'newest' => $newest] = boostPlacementFixture(); $owner = User::factory()->create(['is_active' => true]); app(CreditWalletService::class)->credit($owner, 40); // Beide Seiten in den Cache legen — noch ohne Boost. expect(app(HomePageData::class)->build(Portal::Presseecho)['leadRelease']->id)->toBe($newest->id); $this->get('https://presseecho.test/de/category/energie-klima') ->assertSuccessful() ->assertDontSeeText('Top-Platzierung'); app(BoostService::class)->boost($owner, $boosted, 7); // Kein Warten auf die TTL: die Buchung verwirft die gecachten Seiten. expect(app(HomePageData::class)->build(Portal::Presseecho)['leadRelease']->id)->toBe($boosted->id); $this->get('https://presseecho.test/de/category/energie-klima') ->assertSuccessful() ->assertSeeText('Top-Platzierung'); }); test('a boosted release leads its category page and is labelled', function () { /** @var TestCase $this */ ['boosted' => $boosted] = boostPlacementFixture(); Boost::factory()->for($boosted)->create(); // Lead-Slot der Branchenseite: Badge und geboosteter Titel im Hero, // die neuere unbezahlte Meldung folgt dahinter in den Top-Meldungen. $this->get('https://presseecho.test/de/category/energie-klima') ->assertSuccessful() ->assertSeeTextInOrder([ 'Top-Platzierung', 'Geboostete aeltere Meldung', 'Neueste unbezahlte Meldung', ]); }); test('a category boost leads its category page but never the homepage', function () { /** @var TestCase $this */ ['boosted' => $boosted, 'newest' => $newest] = boostPlacementFixture(); Boost::factory()->for($boosted)->category()->create(); // Branchenseite: Kategorie-Boost besetzt den Lead-Slot. $this->get('https://presseecho.test/de/category/energie-klima') ->assertSuccessful() ->assertSeeTextInOrder(['Top-Platzierung', 'Geboostete aeltere Meldung']); // Startseite: bleibt bei der Chronologie — der Kategorie-Boost kauft // sie nicht mit (Entscheidung 13.07.). $data = app(HomePageData::class)->build(Portal::Presseecho); expect($data['leadRelease']->id)->toBe($newest->id) ->and($data['boostedIds'])->toBe([]); }); test('category boosts take the category slots ahead of homepage boosts', function () { /** @var TestCase $this */ ['boosted' => $categoryBoosted, 'newest' => $newest, 'category' => $category, 'company' => $company] = boostPlacementFixture(); // Neuere PM mit Startseiten-Boost — auf der Branchenseite hat der // Kategorie-Boost trotzdem Vorrang (seine 3 Slots sind die Garantie). $homeBoosted = PressRelease::factory() ->published() ->forPortal(Portal::Presseecho) ->for($category) ->for($company) ->create([ 'title' => 'Startseiten-Boost Meldung', 'slug' => 'startseiten-boost-meldung', 'published_at' => now()->subHour(), 'classification' => PressReleaseClassification::Green->value, ]); Boost::factory()->for($categoryBoosted)->category()->create(); Boost::factory()->for($homeBoosted)->create(); // Ohne Vorrang würde die neuere Startseiten-Boost-Meldung den Lead // chronologisch gewinnen — der Kategorie-Boost muss vorne stehen. $this->get('https://presseecho.test/de/category/energie-klima') ->assertSuccessful() ->assertSeeTextInOrder([ 'Geboostete aeltere Meldung', 'Startseiten-Boost Meldung', ]); }); test('booking a category boost invalidates the category page but keeps the homepage cache', function () { /** @var TestCase $this */ ['boosted' => $boosted, 'newest' => $newest] = boostPlacementFixture(); $owner = User::factory()->create(['is_active' => true]); app(CreditWalletService::class)->credit($owner, 40); // Beide Seiten in den Cache legen — noch ohne Boost. expect(app(HomePageData::class)->build(Portal::Presseecho)['leadRelease']->id)->toBe($newest->id); $this->get('https://presseecho.test/de/category/energie-klima') ->assertSuccessful() ->assertDontSeeText('Top-Platzierung'); app(BoostService::class)->boost($owner, $boosted, 7, BoostPlacement::Category); // Branchenseite sofort aktualisiert, Startseiten-Cache bleibt stehen // (der Kategorie-Boost berührt die Startseite nicht). expect(Cache::has('web.home.presseecho.de'))->toBeTrue(); $this->get('https://presseecho.test/de/category/energie-klima') ->assertSuccessful() ->assertSeeText('Top-Platzierung'); });