presseportale/tests/Feature/Web/TransparencyPagesTest.php
Kevin Adametz 5dcaf4e7b0 web: E9 und E10 entschieden — Zuordnung aktiv, eine Adresse je statischer Seite, Hub-Portalseiten leiten weiter
E9: Kategorie-Zuordnung wie vorgeschlagen übernommen, Zuordnung ist
standardmäßig an (CANONICAL_ASSIGNMENT_ENABLED). Auf der Dev-Kopie
verifiziert: Kopie trägt rel=canonical auf die Erstquelle und fällt aus der
Sitemap ihres Portals.

E10: faq, hilfe, gute-pressemitteilung, die vier Transparenzseiten und die
API-Doku lagen auf allen drei Hosts mit identischem Text und je eigenem
Self-Canonical. Jetzt führt presseecho die Informationsseiten, der Hub die
API-Doku; Sitemap und llms.txt nennen nur die führende Adresse.
Rechtstexte (Impressum, Datenschutz, AGB, Richtlinien, Meldeweg,
Kündigung) bleiben bewusst je Host — jede Domain ist ein eigenes Angebot
mit eigener Anbieterkennzeichnung.

Bei der Erreichbarkeitsprüfung des Hubs gefunden und behoben:
/de/preise, /de/veroeffentlichen, /de/newsrooms und /de/suche wurden unter
pressekonto.com mit dem Branding von businessportal24 ausgeliefert (der
Portal-Kontext fällt dort darauf zurück) — jetzt 301 auf presseecho, die
Transparenz-Navigation verlinkt direkt dorthin. /de/kontakt und
/de/ueber-uns sprangen über /de auf die Startseite, jetzt in einem Schritt.

Der Self-Canonical kommt jetzt aus dem aufgerufenen Host; Portal-Inhalte,
die über den Hub laufen, behalten ihr Portal (webPortalContextApplied).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 07:52:09 +00:00

131 lines
6.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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',
'https://presseecho.test/en/pressemitteilung-kostenlos-veroeffentlichen',
]);
test('the free page answers honestly, names our own link policy and shows the portal list only when configured', function () {
/** @var TestCase $this */
config()->set('transparency.free_portals', []);
$response = $this->get('https://businessportal24.test/de/pressemitteilung-kostenlos-veroeffentlichen')->assertSuccessful();
$response
->assertSee('Pressemitteilung kostenlos veröffentlichen')
->assertSee('Einen kostenlosen Weg gibt es nicht')
->assertSee('<code>rel="sponsored nofollow"</code>', false)
->assertSee('Warum kostenfreie Annahme und Prüfung sich ausschließen')
->assertSee('Wer bei kostenlosen Angeboten am Ende zahlt')
->assertDontSee('Kostenlose Presseportale</h2>', false)
->assertSee('aria-current="page">Kostenlos veröffentlichen?</span>', false)
->assertSee('/de/pressemitteilung-veroeffentlichen-ohne-abo', false);
config()->set('transparency.free_portals', [['name' => 'Beispielportal', 'url' => 'https://portal.example', 'note' => 'nur Text, keine Links']]);
config()->set('transparency.free_portals_checked_at', '2026-09-17');
$this->get('https://businessportal24.test/de/pressemitteilung-kostenlos-veroeffentlichen')->assertSuccessful()
->assertSee('Kostenlose Presseportale</h2>', false)
->assertSee('am 17.09.2026 kostenlos Einreichungen angenommen haben')
->assertSee('<a href="https://portal.example" rel="nofollow noopener" target="_blank">Beispielportal</a>', false)
->assertSee('nur Text, keine Links')
->assertSee('Kein Eintrag ist eine Empfehlung.');
});
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?')
// Unter-Navigation: aktuelle Seite markiert, Preisseite verlinkt.
->assertSee('aria-current="page">Meldung löschen oder ändern</span>', false)
->assertSee('/de/preise" class=', false);
});
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('aria-label="Transparenz"', false)
->assertSee('aria-current="page">Tarife &amp; Preise</span>', false)
->assertSee('/de/was-kostet-eine-pressemitteilung', false)
->assertSee('/de/pressemitteilung-veroeffentlichen-ohne-abo', false)
->assertSee('/de/pressemitteilung-loeschen-oder-aendern-lassen', false)
->assertSee('/de/pressemitteilung-kostenlos-veroeffentlichen', false);
$this->get('https://presseecho.test/de/hilfe')->assertSuccessful()
->assertSee('/de/pressemitteilung-loeschen-oder-aendern-lassen', false)
->assertSee('/de/pressemitteilung-veroeffentlichen-ohne-abo', false)
->assertSee('/de/pressemitteilung-kostenlos-veroeffentlichen', false);
$this->get('https://presseecho.test/sitemap.xml')->assertSuccessful()
->assertSee('<loc>https://presseecho.test/de/was-kostet-eine-pressemitteilung</loc>', false)
->assertSee('<loc>https://presseecho.test/de/pressemitteilung-kostenlos-veroeffentlichen</loc>', false);
$this->get('https://presseecho.test/llms.txt')->assertSuccessful()
->assertSee('/de/pressemitteilung-kostenlos-veroeffentlichen', false);
// Die Seiten liegen auf allen drei Hosts; in der Sitemap steht nur die
// führende Adresse (Anhang 2, E10) — beim Hub bleibt die API-Doku.
$this->get('https://pressekonto.test/sitemap.xml')->assertSuccessful()
->assertDontSee('/de/pressemitteilung-loeschen-oder-aendern-lassen', false)
->assertSee('<loc>https://pressekonto.test/de/api</loc>', false);
});