feat: komplette Site — 19 Routen, 24 Artikel, Leistungen, IT-Service, FAQ, Kontakt, CRM-Formular
This commit is contained in:
@@ -2,11 +2,13 @@
|
|||||||
"""IT-Hilfe-Sofort.de — Flask-Webapp für IT-Dienstleistungen im Gesundheitswesen."""
|
"""IT-Hilfe-Sofort.de — Flask-Webapp für IT-Dienstleistungen im Gesundheitswesen."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).parent
|
BASE_DIR = Path(__file__).parent
|
||||||
|
ARTIKEL_DIR = BASE_DIR
|
||||||
PORT = int(os.environ.get("PORT", "5003"))
|
PORT = int(os.environ.get("PORT", "5003"))
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@@ -124,6 +126,426 @@ def datenschutz() -> str:
|
|||||||
return _base_template("Datenschutz", content)
|
return _base_template("Datenschutz", content)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Blog-Artikel-Datenbank ───────────────────────────────
|
||||||
|
_ARTIKEL = [
|
||||||
|
(
|
||||||
|
"/blog/telematik-vorschriften",
|
||||||
|
"TI-Anschlusspflicht 2025",
|
||||||
|
"artikel-01-ti-anschlusspflicht.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/telematik-vorschriften",
|
||||||
|
"Elektronische Abrechnung ab 12/2026",
|
||||||
|
"artikel-02-elektronische-abrechnung.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/telematik-vorschriften",
|
||||||
|
"SMC-B-Karte — 33% Abdeckung",
|
||||||
|
"artikel-03-smc-b-karte.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/dsgvo-it-sicherheit",
|
||||||
|
"46,9 Mio. EUR DSGVO-Bugelder",
|
||||||
|
"artikel-04-dsgvo-bussgelder.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/dsgvo-it-sicherheit",
|
||||||
|
"BSI: 16 Schwachstellen Pflegesoftware",
|
||||||
|
"artikel-05-bsi-schwachstellen.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/dsgvo-it-sicherheit",
|
||||||
|
"138 Sicherheitsvorfaelle Gesundheitswesen",
|
||||||
|
"artikel-06-sicherheitsvorfaelle.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/telematik-vorschriften",
|
||||||
|
"NIS-2 Registrierungsfrist",
|
||||||
|
"artikel-07-nis2.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/fachkraeftemangel",
|
||||||
|
"200.000 unbesetzte Pflegestellen",
|
||||||
|
"artikel-08-fachkraeftemangel.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/fachkraeftemangel",
|
||||||
|
"62% Altenpflege — Personalplanung",
|
||||||
|
"artikel-09-personalplanung.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/fachkraeftemangel",
|
||||||
|
"KI-Sprachassistent spart 27% Zeit",
|
||||||
|
"artikel-10-ki-sprachassistent.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/foerderung-finanzierung",
|
||||||
|
"Bis 12.000 EUR Digitalisierungszuschuss",
|
||||||
|
"artikel-11-digitalisierungszuschuss.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/foerderung-finanzierung",
|
||||||
|
"DiPA — 70 EUR monatlich ab 2026",
|
||||||
|
"artikel-12-dipa.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/foerderung-finanzierung",
|
||||||
|
"Pflegeversicherung 1 Mrd. Defizit",
|
||||||
|
"artikel-13-pflegeversicherung.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/qualitaet-pruefung",
|
||||||
|
"QPR ambulant ab Juli 2026",
|
||||||
|
"artikel-14-qpr-ambulant.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/qualitaet-pruefung",
|
||||||
|
"PeBeM verpflichtend ab 2026",
|
||||||
|
"artikel-15-pebem.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/qualitaet-pruefung",
|
||||||
|
"MDK-Pruefung digital bestehen",
|
||||||
|
"artikel-16-mdk-pruefung.html",
|
||||||
|
),
|
||||||
|
("/blog/telematik-vorschriften", "ePA fuer alle — Opt-out", "artikel-17-epa.html"),
|
||||||
|
(
|
||||||
|
"/blog/dsgvo-it-sicherheit",
|
||||||
|
"Cloud-Hosting Deutschland",
|
||||||
|
"artikel-18-cloud-hosting.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/praxis-tipps",
|
||||||
|
"Tablets fuer die Pflege — Top 5",
|
||||||
|
"artikel-19-tablets.html",
|
||||||
|
),
|
||||||
|
("/blog/praxis-tipps", "WLAN im Pflegeheim", "artikel-20-wlan.html"),
|
||||||
|
("/blog/praxis-tipps", "Offline-Dokumentation", "artikel-21-offline.html"),
|
||||||
|
(
|
||||||
|
"/blog/dsgvo-it-sicherheit",
|
||||||
|
"E-Mail-Verschluesselung",
|
||||||
|
"artikel-22-email-verschluesselung.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/praxis-tipps",
|
||||||
|
"Medikationsplan digital",
|
||||||
|
"artikel-23-medikationsplan.html",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/blog/praxis-tipps",
|
||||||
|
"IT-Notfallplan 10-Punkte",
|
||||||
|
"artikel-24-it-notfallplan.html",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# ── Leistungen-Daten ─────────────────────────────────────
|
||||||
|
_LEISTUNGEN = {
|
||||||
|
"pflegedienste": {
|
||||||
|
"titel": "IT-Loesungen fuer ambulante Pflegedienste",
|
||||||
|
"cards": [
|
||||||
|
(
|
||||||
|
"Tourenplanung",
|
||||||
|
"Digitale Tourenplanung und mobile Datenerfassung vor Ort. Effizientere Routen und Dokumentation auf dem Tablet.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Mobile Dokumentation",
|
||||||
|
"Echtzeit-Dokumentation von Pflegemaßnahmen direkt im Einsatz. Synchronisiert mit der zentralen Verwaltung.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Abrechnung und Reporting",
|
||||||
|
"Automatisierte Abrechnung und Auswertungen. Weniger Verwaltungszeit, mehr Zeit fuer Patienten.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"pflegeheime": {
|
||||||
|
"titel": "IT-Loesungen fuer stationaere Pflegeheime",
|
||||||
|
"cards": [
|
||||||
|
(
|
||||||
|
"Heimverwaltung",
|
||||||
|
"Digitale Heimpleitung: Dienstplanung, Raummanagement, Budget-Controller. Alles in einer Plattform.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Biometrie und Zutrittssysteme",
|
||||||
|
"Fingerabdruck- und Gesichtserkennung fuer sicheren Zutritt. DSGVO-konform und benutzerfreundlich.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Dokumentationssysteme",
|
||||||
|
"Komplette digitale Dokumentation von Pflegeplanung bis Medikationsueberwachung. Vollstaendig auditfahig.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"tagespflegen": {
|
||||||
|
"titel": "IT-Loesungen fuer Tagespflegen",
|
||||||
|
"cards": [
|
||||||
|
(
|
||||||
|
"Terminbuchung",
|
||||||
|
"Online-Terminbuchung mit Kalendersynchronisation. Patienten koennen Termine direkt vereinbaren.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Fahrdienstverwaltung",
|
||||||
|
"Organisation von Fahrdiensten und Kurieren. Automatische Zuweisung und Echtzeit-Tracking.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Aktivierungsangebote",
|
||||||
|
"Digitales Planungs- und Monitoring-System fuer Aktivitaeten und Therapie-Programme im Tagesverlauf.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── IT-Service-Daten ─────────────────────────────────────
|
||||||
|
_IT_SERVICE = {
|
||||||
|
"hardware": {
|
||||||
|
"titel": "IT-Hardware fuer das Gesundheitswesen",
|
||||||
|
"cards": [
|
||||||
|
(
|
||||||
|
"Server und Workstations",
|
||||||
|
"Auswahl, Installation und Wartung von Servern und Workstations. DSGVO-konform und skalierbar.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Thin Clients und Endgeraete",
|
||||||
|
"Kosteneffiziente Thin Clients mit zentralem Management. Einfache Administration, hohe Sicherheit.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Drucker und Peripherie",
|
||||||
|
"Medizinische Geraete und Drucker mit Spezialtreibern. Integration in bestehende Netzwerke.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Beschaffung und Consulting",
|
||||||
|
"Unabhaengige Beratung zur Hardware-Auswahl. Gutes Preis-Leistungs-Verhaeltnis garantiert.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"netzwerk": {
|
||||||
|
"titel": "IT-Netzwerk fuer Pflegeeinrichtungen",
|
||||||
|
"cards": [
|
||||||
|
(
|
||||||
|
"WLAN-Ausleuchtung",
|
||||||
|
"Professionelle WLAN-Planung und -Implementierung fuer komplette Einrichtungen. Lueckenlose Abdeckung.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"VPN und Remote-Zugang",
|
||||||
|
"Verschluesselte Verbindung fuer mobiles Arbeiten. Sichere Fernwartung und Fernadministration.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Firewall und Segmentierung",
|
||||||
|
"Zwei-Wege-Firewall mit medizinischen Geraete-Portabilitaet. Trennung von Betriebs- und Pflegedaten.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Netzwerkmonitoring",
|
||||||
|
"24/7 Ueberwachung aller Netzwerk-Komponenten. Proaktive Stoerungserkennung und automatische Eskalation.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"email-kommunikation": {
|
||||||
|
"titel": "E-Mail und Kommunikation",
|
||||||
|
"cards": [
|
||||||
|
(
|
||||||
|
"Microsoft 365",
|
||||||
|
"Vollstaendige Implementierung und Verwaltung von Microsoft 365. Cloud-Hosting in Deutschland verfuegbar.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Exchange und VoIP",
|
||||||
|
"E-Mail-Server mit Exchange-Integration und VoIP-Telefonie. Nahtlose Kommunikation aller Abteilungen.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Spam-Schutz und Archivierung",
|
||||||
|
"Mehrschichtiger Spam-Schutz und automatische Archivierung fuer Compliance und Aufbewahrungspflichten.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"support-wartung": {
|
||||||
|
"titel": "IT-Support und Wartung",
|
||||||
|
"cards": [
|
||||||
|
(
|
||||||
|
"SLA-Stufen",
|
||||||
|
"Flexible Service-Level-Agreements: von Standard bis Mission-Critical. Garantierte Reaktionszeiten fuer alle Stoerungen.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Reaktionszeiten",
|
||||||
|
"Erste Antwort innerhalb von 30 Minuten. Vollstaendige Loesung innerhalb von 4 Stunden bei schwerwiegenden Problemen.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Fernwartung und Hosting",
|
||||||
|
"Sichere Fernwartung mit Echtzeit-Desktop-Zugriff. DSGVO-konforme Hostung aller Systeme.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Monitoring und Wartung",
|
||||||
|
"Automatisiertes Monitoring aller IT-Komponenten. Regelmassige Sicherheitsupdates und Wartung in Einweisungszeiten.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ── Helper: Seitenleiste ──────────────────────────────────
|
||||||
|
def _sidebar(routes: list[tuple[str, str, str]]) -> str:
|
||||||
|
"""Erstelle Seitenleiste mit Artikel-Links.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
routes: Liste von (Route, Titel, Datei)-Tupeln.
|
||||||
|
"""
|
||||||
|
links = []
|
||||||
|
for route, titel, _datei in routes:
|
||||||
|
links.append('<a href="' + route + '" class="sidebar-link">' + titel + "</a>")
|
||||||
|
return (
|
||||||
|
"<div class='article-sidebar'><h4 class='sidebar-title'>Weitere Artikel</h4>"
|
||||||
|
+ "".join(links)
|
||||||
|
+ "</div>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Helper: Karten bauen ─────────────────────────────────
|
||||||
|
def _cards(titel: str, beschreibung: str) -> str:
|
||||||
|
"""Erstelle eine Service-Karte.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
titel: Titel der Karte.
|
||||||
|
beschreibung: Beschreibungstext.
|
||||||
|
"""
|
||||||
|
return '<div class="card"><h3>' + titel + "</h3><p>" + beschreibung + "</p></div>"
|
||||||
|
|
||||||
|
|
||||||
|
# ── Weitere Routen ───────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/ueber-uns")
|
||||||
|
def ueber_uns() -> str:
|
||||||
|
"""Ueber uns."""
|
||||||
|
content = '<div class="content"><h1>Ueber uns</h1><p>Daniel Krause ist IT-Berater im Gesundheitswesen. Werte: Transparenz, Schnelligkeit, DSGVO-Compliance.</p></div>'
|
||||||
|
return _base_template("Ueber uns", content)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/kontakt")
|
||||||
|
def kontakt() -> str:
|
||||||
|
"""Kontakt mit CRM-Formular."""
|
||||||
|
content = (
|
||||||
|
'<div class="content" style="max-width:900px">'
|
||||||
|
"<h1>Kontakt</h1>"
|
||||||
|
'<div style="display:grid;gap:2rem">'
|
||||||
|
'<div class="contact-info">'
|
||||||
|
"<p>Wir beraten Sie gerne persoenlich. Sprechen Sie uns an!</p>"
|
||||||
|
"<p><strong>E-Mail:</strong> info@it-hilfe-sofort.de</p>"
|
||||||
|
"</div>"
|
||||||
|
'<div style="border:1px solid #E5E7EB;border-radius:8px;overflow:hidden">'
|
||||||
|
'<iframe src="https://crm.companyhub.io/widget/form/iEDHWbU9iII32UkhXj2b" style="width:100%;height:946px;border:none" title="Kontaktformular"></iframe>'
|
||||||
|
"</div>"
|
||||||
|
"</div></div>"
|
||||||
|
)
|
||||||
|
return _base_template("Kontakt", content)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/faq")
|
||||||
|
def faq() -> str:
|
||||||
|
"""FAQ."""
|
||||||
|
fragen: list[tuple[str, str]] = [
|
||||||
|
("Wie schnell sind Sie vor Ort?", "Innerhalb von 24—48 Stunden."),
|
||||||
|
("Was kostet der IT-Support?", "Ab 29 EUR/Monat pro Geraet."),
|
||||||
|
(
|
||||||
|
"Uebernehmen Sie die komplette IT?",
|
||||||
|
"Ja, von der Installation bis zum Monitoring.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Wie stellen Sie DSGVO-Compliance sicher?",
|
||||||
|
"Hosting in Deutschland, Verschlusselung, Zugangskontrollen.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Bieten Sie auch Schulungen an?",
|
||||||
|
"Ja, fuer alle Mitarbeiter Ihrer Einrichtung.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Wie laeuft der Onboarding ab?",
|
||||||
|
"Kostenlose Erstberatung, dann Umsetzung in 2—4 Wochen.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Koennen Sie auf bestehende Systeme aufsetzen?",
|
||||||
|
"Ja, wir arbeiten mit allen gaeingen Pflegesoftwaren.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Gibt es einen Notfallservice?",
|
||||||
|
"Ja, 24/7 Notfallservice fuer kritische Stoerungen.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
fragen_html_parts: list[str] = []
|
||||||
|
for f, t in fragen:
|
||||||
|
fragen_html_parts.append(
|
||||||
|
"<div class='faq-item'><summary>"
|
||||||
|
+ f
|
||||||
|
+ "</summary><div class='answer'>"
|
||||||
|
+ t
|
||||||
|
+ "</div></div>"
|
||||||
|
)
|
||||||
|
fragen_html = "".join(fragen_html_parts)
|
||||||
|
content = "<div class='content'><h1>Haeufige Fragen</h1>" + fragen_html + "</div>"
|
||||||
|
return _base_template("FAQ", content)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/blog/<path:slug>")
|
||||||
|
def blog(slug: str) -> str:
|
||||||
|
"""Blog-Artikel."""
|
||||||
|
datei = "artikel-" + slug + ".html"
|
||||||
|
pfad = ARTIKEL_DIR / datei
|
||||||
|
if pfad.exists():
|
||||||
|
inhalt = pfad.read_text(encoding="utf-8")
|
||||||
|
# Extrahiere Body-Inhalt aus dem Artikel-HTML
|
||||||
|
match = re.search(r"<body[^>]*>(.*?)</body>", inhalt, re.DOTALL | re.IGNORECASE)
|
||||||
|
inhalt = match.group(1) if match else inhalt
|
||||||
|
else:
|
||||||
|
inhalt = "<p>Artikel nicht gefunden.</p>"
|
||||||
|
titel = slug.replace("-", " ").title()
|
||||||
|
content = (
|
||||||
|
"<div class='article-layout'><div class='article-content'>" + inhalt + "</div>"
|
||||||
|
)
|
||||||
|
# Seitenleiste: alle anderen Artikel derselben Sektion
|
||||||
|
sektion_routen = [
|
||||||
|
(r, t, d) for r, t, d in _ARTIKEL if r == "/blog/" + slug.replace("-", "")
|
||||||
|
]
|
||||||
|
if not sektion_routen:
|
||||||
|
sektion_routen = _ARTIKEL
|
||||||
|
sidebar = _sidebar(sektion_routen)
|
||||||
|
content += sidebar + "</div>"
|
||||||
|
return _base_template("Blog: " + titel, content, active="blog")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/leistungen/<path:slug>")
|
||||||
|
def leistungen(slug: str) -> str:
|
||||||
|
"""Leistungs-Seite."""
|
||||||
|
data = _LEISTUNGEN.get(slug)
|
||||||
|
if not data:
|
||||||
|
content = "<div class='content'><h1>Leistung nicht gefunden</h1></div>"
|
||||||
|
return _base_template("Leistungen", content)
|
||||||
|
cards_items = [_cards(titel, beschreibung) for titel, beschreibung in data["cards"]]
|
||||||
|
cards_all = (
|
||||||
|
'<div class="card-grid" style="margin-top:2rem">'
|
||||||
|
+ "".join(cards_items)
|
||||||
|
+ "</div>"
|
||||||
|
)
|
||||||
|
content = (
|
||||||
|
"<div class='content'><h1>" + data["titel"] + "</h1>" + cards_all + "</div>"
|
||||||
|
)
|
||||||
|
return _base_template(data["titel"], content, active="leistungen")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/it-service/<path:slug>")
|
||||||
|
def it_service(slug: str) -> str:
|
||||||
|
"""IT-Service-Seite."""
|
||||||
|
data = _IT_SERVICE.get(slug)
|
||||||
|
if not data:
|
||||||
|
content = "<div class='content'><h1>Service nicht gefunden</h1></div>"
|
||||||
|
return _base_template("IT-Service", content)
|
||||||
|
cards_items = [_cards(titel, beschreibung) for titel, beschreibung in data["cards"]]
|
||||||
|
cards_all = (
|
||||||
|
'<div class="card-grid" style="margin-top:2rem">'
|
||||||
|
+ "".join(cards_items)
|
||||||
|
+ "</div>"
|
||||||
|
)
|
||||||
|
content = (
|
||||||
|
"<div class='content'><h1>" + data["titel"] + "</h1>" + cards_all + "</div>"
|
||||||
|
)
|
||||||
|
return _base_template(data["titel"], content, active="it-service")
|
||||||
|
|
||||||
|
|
||||||
|
# ── Main ──────────────────────────────────────────────────────────────
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print(f"IT-Hilfe-Sofort.de startet auf http://127.0.0.1:{PORT}")
|
print(f"IT-Hilfe-Sofort.de startet auf http://127.0.0.1:{PORT}")
|
||||||
app.run(host="127.0.0.1", port=PORT, debug=False)
|
app.run(host="127.0.0.1", port=PORT, debug=False)
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
"""Conftest fuer IT-Hilfe-Sofort Tests — sys.path setup."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Projekt root ins sys.path (muss vor pytest import app! kommen)
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
"""IT-Hilfe-Sofort.de — alle 19 Sitemap-Routen."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||||
|
project_root = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
import app # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client():
|
||||||
|
"""Flask-Test-Client."""
|
||||||
|
app.app.config["TESTING"] = True
|
||||||
|
with app.app.test_client() as klient:
|
||||||
|
yield klient
|
||||||
|
|
||||||
|
|
||||||
|
SEITEN = [
|
||||||
|
("/", "Startseite"),
|
||||||
|
("/impressum", "Impressum"),
|
||||||
|
("/datenschutz", "Datenschutz"),
|
||||||
|
("/ueber-uns", "Ueber uns"),
|
||||||
|
("/kontakt", "Kontakt"),
|
||||||
|
("/faq", "FAQ"),
|
||||||
|
("/blog/telematik-vorschriften", "Telematik-Vorschriften"),
|
||||||
|
("/blog/dsgvo-it-sicherheit", "DSGVO & IT-Sicherheit"),
|
||||||
|
("/blog/fachkraeftemangel", "Fachkraeftemangel"),
|
||||||
|
("/blog/foerderung-finanzierung", "Foerderung & Finanzierung"),
|
||||||
|
("/blog/qualitaet-pruefung", "Qualitaet & Pruefung"),
|
||||||
|
("/blog/praxis-tipps", "Praxis-Tipps"),
|
||||||
|
("/leistungen/pflegedienste", "Pflegedienste"),
|
||||||
|
("/leistungen/pflegeheime", "Pflegeheime"),
|
||||||
|
("/leistungen/tagespflegen", "Tagespflegen"),
|
||||||
|
("/it-service/hardware", "Hardware"),
|
||||||
|
("/it-service/netzwerk", "Netzwerk"),
|
||||||
|
("/it-service/email-kommunikation", "E-Mail & Kommunikation"),
|
||||||
|
("/it-service/support-wartung", "Support & Wartung"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("pfad,titel", SEITEN)
|
||||||
|
def test_seite_ist_erreichbar(pfad: str, titel: str, client) -> None:
|
||||||
|
"""Jede Sitemap-Seite muss mit 200 antworten."""
|
||||||
|
response = client.get(pfad)
|
||||||
|
assert response.status_code == 200, f"{pfad} ({titel}) gab {response.status_code}"
|
||||||
|
assert b"<!DOCTYPE html>" in response.data, f"{pfad} hat kein valid HTML"
|
||||||
|
|
||||||
|
|
||||||
|
def test_base_template_inhalt(client) -> None:
|
||||||
|
"""Das base_template muss Header, Footer und CSS enthalten."""
|
||||||
|
response = client.get("/")
|
||||||
|
assert b"site-header" in response.data
|
||||||
|
assert b"site-footer" in response.data
|
||||||
|
assert b"IT-Hilfe-Sofort.de" in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_kontakt_iframe_present(client) -> None:
|
||||||
|
"""Das Kontakt-Formular muss das CRM iframe enthalten."""
|
||||||
|
response = client.get("/kontakt")
|
||||||
|
assert b"crm.companyhub.io" in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_faq_accordion_present(client) -> None:
|
||||||
|
"""FAQ muss Aufklappbare Elemente enthalten."""
|
||||||
|
response = client.get("/faq")
|
||||||
|
assert b"faq-item" in response.data
|
||||||
|
assert b"summary" in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_404_fuer_unbekannte_seiten(client) -> None:
|
||||||
|
"""Unbekannte URLs muessen 404 liefern."""
|
||||||
|
response = client.get("/nicht-existiert")
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_artikel_dateien_existieren() -> None:
|
||||||
|
"""Alle 24 Artikel-Htmls muessen im 'artikel'-Ordner liegen."""
|
||||||
|
artikel_dir = project_root
|
||||||
|
zaehler = 0
|
||||||
|
for f in artikel_dir.glob("artikel-*.html"):
|
||||||
|
zaehler += 1
|
||||||
|
assert zaehler == 24, f"Erwartet 24 Artikel, gefunden {zaehler}"
|
||||||
Reference in New Issue
Block a user