web: Transparenzseiten — was kostet, ohne Abo, loeschen/aendern (Roadmap 2.4)

Drei Seiten mit unterschiedlicher Suchintention, DE/EN, Hub + Spokes:
- /was-kostet-eine-pressemitteilung: kurze Antwort im ersten Bildschirm,
  vier Abrechnungsmodelle ohne Anbieternamen und fremde Preise, sechs
  Prueffragen fuer Folgekosten, "Was bei uns gilt" (sieben Zusagen mit
  Einloesung), Rechenbeispiel Einzelabrechnung gegen Starter-Monate mit
  Zeilen fuer beide Seiten.
- /pressemitteilung-veroeffentlichen-ohne-abo: Ablauf, enthalten / nicht
  enthalten, nichts verfaellt, wann sich ein Tarif lohnt.
- /pressemitteilung-loeschen-oder-aendern-lassen: fuenf Wege als
  Tabelle (Korrektur, Aenderung, DSGVO, Depublikation, DSA), Antragsweg
  ueber /melden/pm, Korrektur statt Loeschung, Absatz fuer Meldungen auf
  fremden Portalen ohne Zusagen ueber Dritte.

PricingTransparencyData liefert alle Zahlen aus config/billing, credits,
legal und dem Starter-Tarif; TransparencyPageController leitet die Marke
aus dem Host ab. Tabellen-Typografie fuer .pm-body. Verlinkt aus
Preisuebersicht, Hilfe-Center, Sitemaps und llms.txt.

Test TransparencyPagesTest (9) erzwingt u. a., dass im Rechenbeispiel
beide Modelle Zeilen gewinnen. Protokoll Teil 5, Roadmap 2.4 abgehakt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-17 12:00:28 +00:00
parent 84ea617107
commit b6e4bc5dc0
18 changed files with 953 additions and 4 deletions

View file

@ -0,0 +1,87 @@
<?php
use App\Models\Plan;
use App\Services\Web\PricingTransparencyData;
use Tests\TestCase;
/**
* Transparenzseiten (Roadmap 2.4): Zahlen aus der Konfiguration, kein
* Anbietername, Rechenbeispiel mit gemischtem Ausgang.
*/
beforeEach(function (): void {
config()->set('billing.single_pm_price_cents', 1900);
config()->set('billing.change_price_cents', 1000);
config()->set('billing.depublication_price_cents', 3500);
config()->set('billing.vat_rate', 0.19);
config()->set('credits.extra_pm.starter', 15);
Plan::factory()->create(['slug' => 'starter', 'name' => 'Starter', 'monthly_price_cents' => 2900, 'yearly_price_cents' => 29000, 'press_release_quota' => 3, 'is_active' => true]);
});
test('the worked example lets pay-per-release win in some rows and the plan in others', function () {
$data = app(PricingTransparencyData::class)->build();
expect($data['singleNet'])->toBe('19')
->and($data['singleGross'])->toBe('22,61')
->and($data['starterMonthlyNet'])->toBe('29')
->and(collect($data['examples'])->pluck('cheaper')->unique()->sort()->values()->all())->toBe(['single', 'starter']);
});
test('the cost page shows the short answer with the configured price and no competitor names', function () {
/** @var TestCase $this */
$response = $this->get('https://presseecho.test/de/was-kostet-eine-pressemitteilung')->assertSuccessful();
$response
->assertSee('Was kostet eine Pressemitteilung?')
->assertSee('<strong>19 € netto</strong> (22,61 € inkl. 19 % USt.)', false)
->assertSee('Woran man Folgekosten erkennt')
->assertSee('Rechenbeispiel')
->assertSee('228&nbsp;€', false)
->assertSee('10 € netto')
->assertSee('35 € netto')
->assertSee('/de/pressemitteilung-loeschen-oder-aendern-lassen', false)
->assertSee('<title>Was kostet eine Pressemitteilung? Preis, Modelle, Rechenbeispiel – presseecho</title>', false);
// Kein Anbietername, keine fremden Preise (Briefing 17.09.2026).
$this->assertStringNotContainsStringIgnoringCase('presseportal.de', (string) $response->getContent());
$this->assertStringNotContainsStringIgnoringCase('openpr', (string) $response->getContent());
});
test('the no-subscription page and the change page render on every host in both editions', function (string $url) {
/** @var TestCase $this */
$this->get($url)->assertSuccessful();
})->with([
'https://businessportal24.test/de/pressemitteilung-veroeffentlichen-ohne-abo',
'https://businessportal24.test/en/pressemitteilung-veroeffentlichen-ohne-abo',
'https://pressekonto.test/de/pressemitteilung-loeschen-oder-aendern-lassen',
'https://presseecho.test/en/pressemitteilung-loeschen-oder-aendern-lassen',
'https://pressekonto.test/en/was-kostet-eine-pressemitteilung',
]);
test('the change page names the five ways with their prices and points to the self-service entry', function () {
/** @var TestCase $this */
$this->get('https://businessportal24.test/de/pressemitteilung-loeschen-oder-aendern-lassen')
->assertSuccessful()
->assertSee('Die fünf Wege')
->assertSee('kostenlos')
->assertSee('10 € netto')
->assertSee('35 € netto')
->assertSee('/de/melden/pm', false)
->assertSee('Die Meldung steht auf einem anderen Portal?');
});
test('the pages are linked from the pricing page, the help centre and the sitemap', function () {
/** @var TestCase $this */
$this->get('https://presseecho.test/de/preise')->assertSuccessful()
->assertSee('/de/was-kostet-eine-pressemitteilung', false);
$this->get('https://presseecho.test/de/hilfe')->assertSuccessful()
->assertSee('/de/pressemitteilung-loeschen-oder-aendern-lassen', false)
->assertSee('/de/pressemitteilung-veroeffentlichen-ohne-abo', false);
$this->get('https://presseecho.test/sitemap.xml')->assertSuccessful()
->assertSee('<loc>https://presseecho.test/de/was-kostet-eine-pressemitteilung</loc>', false);
$this->get('https://pressekonto.test/sitemap.xml')->assertSuccessful()
->assertSee('<loc>https://pressekonto.test/de/pressemitteilung-loeschen-oder-aendern-lassen</loc>', false);
});