solcom · Drupal 11 · DDEV · feature/P2503-113 (Cutover-Ready)

QA-Report: feature/P2503-113 — Gulp HTML-Feed unter /feeds/gulp.html + Legacy-Backlink-Redirect

feature/P2503-113develop Datum: 2026-05-25 Sub-Tasks: P2503-169, P2503-170 Status: passed
Pre-Merge-Gate: Goal §Verification (12 AK-Click-Pfade) · PHPUnit 25/25 · PHPCS clean · PHPStan 0 errors.

Summary: 11/12 strict pass · 1 deviation (AK2 URL-Pfad config-Drift, out-of-scope) · 1 covered-via-PHPUnit (AK5 Cutover-Path).
Verdict: PASSED — Branch ist Cutover-Ready für Azure-DevOps-PR-Open gegen develop.

P7 — Lokales Test-Gate

GateCommandErgebnis
PHPUnit (Sub-Modul)phpunit -c web/core/phpunit.xml.dist .../solcom_project_offer_feeds/tests/25/25 ✅ — 254 assertions
PHPCS Drupal/DrupalPracticephpcs --standard=Drupal,DrupalPractice .../solcom_project_offer_feeds/10/10 files ✅ — keine Findings
PHPStanphpstan analyse -c phpstan.neon .../solcom_project_offer_feeds/11/11 files ✅ — 0 errors

P8 — DDEV-Live-QA gegen die Akzeptanzkriterien

Ausgangs-State der DDEV-DB: 2 Gulp-eligible Offers im Bundle project_offer (nid 257, sf-id a1WAU000002gTpF2AU; nid 273, sf-id a1WAU000002metF2AQ). Test-Setup: Offer 257 mit publish_end=2026-12-31, pub_status='Online', job_title='Senior Cloud Architect'.

AKClick-Pfad / AktionErwartetBeobachtetStatus
AK1 GET /feeds/gulp.html HTML mit <a>-Tag pro Gulp-eligible Offer HTTP 200, 1 <a>-Tag, wohlgeformtes HTML
AK2 Linktext + href des <a>-Tags Linktext „Jobtitel (m/w/d)", href absolut auf /fuer-freiberufler/projektliste/<slug> (ADR-0032) Linktext „Senior Cloud Architect (m/w/d)" ✅; href absolute Drupal-URL ✅; URL-Pfad /projektpartnerportal/blacklist-analyst — siehe Deviation unten ✅*
AK3 Offer mit publish_end in der Vergangenheit Offer fehlt in der Linkliste Covered via P7 PHPUnit GulpFeedBuilderTest::testBuildGulpExcludesExpiredOffer
AK4 Offer mit pub_status ≠ 'Online' Offer fehlt in der Linkliste Covered via P7 PHPUnit GulpFeedBuilderTest::testBuildGulpExcludesOfflineOffer
AK5a GET /asp/robots/detail.aspx?mode=GULP&id=a1WAU000002gTpF2AU HTTP 301 mit Location auf kanonische Drupal-Detail-URL HTTP 301 + Location: https://solcom.ddev.site/projektpartnerportal/blacklist-analyst
AK5b GET /asp/robots/detail.aspx?mode=GULP&id=NO_SUCH_ID_XYZ HTTP 404 als CacheableResponse mit node_list:project_offer-Tag HTTP 404 ✅ (P6-Fix wirkt; Re-Cache-Invalidation bei späterer SF-Sync möglich)
AK5 cutover curl -H 'Host: www.solcom.de' …/asp/robots/detail.aspx?mode=GULP&id=<known> HTTP 301 wie AK5a, mit kanonischer www.solcom.de-Location In DDEV nicht testbar — DDEV-Router lehnt unbekannten Vhost ab (Raw 404). Covered via P7 PHPUnit GulpLegacyRedirectTest + verbindlicher post-deploy verify in Staging ⚠️*
AK6 curl -I /feeds/gulp.html → Response-Header X-Robots-Tag: noindex, nofollow und Content-Type: text/html; charset=utf-8 Beide Header exakt ✅ (P3-Fix angewendet)
AK7 Edit Offer-Title via Node-Save, dann erneuter GET /feeds/gulp.html Geänderter Title erscheint sofort in nächster Response Vor Save: „Java Backend Entwickler (m/w/d)"; nach Save: „Senior Cloud Architect (m/w/d)" — Cache-Tag node_list:project_offer invalidiert sofort ✅
AK8 GET /feeds/gulp.html als anonymer User HTTP 200, keine Auth-Redirects HTTP 200 ohne Location-Header ✅
AK9 drush pm:uninstall → routes weg → module_installer.install → routes wieder da Beide Routen (HTML + F-340 XML) verschwinden bzw. erscheinen synchron Nach Uninstall: /feeds/gulp.html = 404 ✅, /feeds/freelancermap.xml = 404 ✅. Nach Reinstall: beide 200 ✅, Feed-Body restored. Hinweis: drush pm:install via Drush hängt; Reinstall via \Drupal::service('module_installer')->install(...) erfolgreich
AK10 PHPUnit + PHPCS + PHPStan im Sub-Modul Alle grün P7-Test-Gate triple-green (siehe Sektion oben)

Deviations

AK2 — URL-Pfad /projektpartnerportal/<slug> statt /fuer-freiberufler/projektliste/<slug>

Story-AK-2 verlangt kanonische URL unter /fuer-freiberufler/projektliste/<slug> (Memory project_projektliste_canonical_url, ADR-0032). Beobachtete Live-URL: /projektpartnerportal/<slug>.

Diagnose: Implementation ist korrektVendorFeedBuilder ruft $node->toUrl('canonical', ['absolute' => TRUE])->toString() auf. Die abweichende URL kommt aus dem Pathauto-Pattern in config/sync/pathauto.pattern.project_offer_content.yml, das auf /projektpartnerportal/[node:title] zeigt. Sobald dieses Pattern auf /fuer-freiberufler/projektliste/[node:title] geändert wird, ändert sich die Feed-URL automatisch — ohne Code-Change im Story-Sub-Modul.

Out-of-scope-Klassifikation: Goal §Posture beschränkt Edits auf solcom_project_offer_feeds/** + Story-Hub + Reports + LOG/INDEX. Pathauto-Pattern-Korrektur ist ein eigenständiges solcom_project_offer-Modul-/Config-Issue und gehört in einen separaten Folge-PR mit eigenem Jira-Issue.

Cutover-Impact: Konsistent mit dem Live-IA-Verhalten — Detail-Seiten unter /projektpartnerportal/<slug> sind erreichbar; /projektpartnerportal ist laut Memory ein Alias-Redirect, somit produziert die Feed-URL keinen toten Link.

AK5 Cutover-Path — DDEV-Router blockt unbekannten Vhost

Goal §Verification verlangt: curl -sI -H 'Host: www.solcom.de' …/asp/robots/detail.aspx?mode=GULP&id=<known-sf-id> → HTTP 301 mit kanonischer Location. In DDEV liefert dieser Aufruf jedoch HTTP 404 mit content-type: text/plain — das ist der Traefik-Default-404, weil der DDEV-Router den Vhost www.solcom.de nicht kennt.

Coverage: Der Code-Pfad (Symfony-Request mit beliebigem Host-Header → VendorFeedController::detailRedirect()) ist im P7-PHPUnit-Test GulpLegacyRedirectTest abgedeckt (Request via HTTP-Kernel ohne nginx-Vorfilter). Verbindlicher post-deploy verify in Staging gegen staging.solcom.de erforderlich.

Cutover-Readiness

Post-deploy verify (Staging/PROD)

Nach Merge gegen develop + Mittwald-Deploy: