feat(help): restructure /help as user-facing Help Center with Zammad integration

- Replace MkDocs redirect with a branded, SEO-optimised Help Center template
- Add sections: Quick Start, Sources, Destinations, Workflows, FAQ, Support
- Integrate optional Zammad live-chat widget and ticket form
- Add config settings: ZAMMAD_URL, ZAMMAD_CHAT_ENABLED, ZAMMAD_CHAT_ID,
  ZAMMAD_FORM_ENABLED, SUPPORT_EMAIL
- Move MkDocs developer docs from /help to /developer-docs
- Move interactive API docs (Swagger/ReDoc) to /admin/api-docs and /admin/api-redoc
- Add API Docs and Developer Docs links to Admin menu (desktop + mobile)
- Update navigation Help link from /help/ to /help
- Update .env.demo with Zammad configuration examples
- Document new settings in docs/ConfigurationGuide.md
- Rewrite tests to cover new Help Center behaviour

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-08 17:25:34 +00:00
parent 78c9df4bb0
commit af3eed4040
8 changed files with 744 additions and 49 deletions
+116 -29
View File
@@ -1,33 +1,50 @@
"""Tests for app/views/help.py module."""
"""Tests for app/views/help.py module user-facing Help Center."""
import pytest
@pytest.mark.integration
class TestHelpViews:
"""Tests for the help documentation view routes."""
"""Tests for the Help Center view routes."""
def test_help_redirect(self, client):
"""GET /help should redirect permanently to /help/."""
response = client.get("/help", follow_redirects=False)
assert response.status_code == 301
assert response.headers["location"] in ("/help/", "http://testserver/help/")
def test_help_returns_200(self, client):
"""GET /help should return the Help Center page."""
response = client.get("/help")
assert response.status_code == 200
def test_help_redirect_follows(self, client):
"""Following /help redirect leads to /help/ (docs served or 404 if not built)."""
# In test environments docs_build/ is not present, so /help/ may 404.
# We only verify the initial redirect works; the final landing page depends
# on whether the docs have been built (they are built only in Docker images).
response = client.get("/help", follow_redirects=False)
assert response.status_code == 301
# Redirect target must be /help/
location = response.headers.get("location", "")
assert location.endswith("/help/")
def test_help_contains_help_center_title(self, client):
"""The page should contain the Help Center heading."""
response = client.get("/help")
assert b"Help Center" in response.content
def test_help_contains_quick_start_section(self, client):
"""The page should contain the Quick Start section."""
response = client.get("/help")
assert b"Quick Start" in response.content
def test_help_contains_sources_section(self, client):
"""The page should contain the Sources section."""
response = client.get("/help")
assert b"Sources" in response.content
def test_help_contains_destinations_section(self, client):
"""The page should contain the Destinations section."""
response = client.get("/help")
assert b"Destinations" in response.content
def test_help_contains_faq_section(self, client):
"""The page should contain the FAQ section."""
response = client.get("/help")
assert b"Frequently Asked Questions" in response.content
def test_help_contains_support_section(self, client):
"""The page should contain the Contact Support section."""
response = client.get("/help")
assert b"Contact Support" in response.content
def test_help_route_is_registered(self, client):
"""Verify the /help route exists in the app router."""
# A GET to /help must not return 405 Method Not Allowed
response = client.get("/help", follow_redirects=False)
response = client.get("/help")
assert response.status_code != 405
@@ -35,31 +52,52 @@ class TestHelpViews:
class TestHelpViewUnit:
"""Unit tests for the help view module."""
def test_help_redirect_returns_301(self):
"""Verify the redirect is HTTP 301 (permanent)."""
def test_help_returns_200(self):
"""Verify the help center returns HTTP 200."""
from fastapi import FastAPI
from fastapi.testclient import TestClient
from starlette.middleware.sessions import SessionMiddleware
from app.views.help import router
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="test-secret")
app.include_router(router)
with TestClient(app) as tc:
resp = tc.get("/help", follow_redirects=False)
assert resp.status_code == 301
resp = tc.get("/help")
assert resp.status_code == 200
def test_help_redirect_target(self):
"""Verify the redirect points to /help/."""
def test_help_page_has_seo_meta(self):
"""Verify SEO meta tags are present in the response."""
from fastapi import FastAPI
from fastapi.testclient import TestClient
from starlette.middleware.sessions import SessionMiddleware
from app.views.help import router
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="test-secret")
app.include_router(router)
with TestClient(app) as tc:
resp = tc.get("/help", follow_redirects=False)
assert resp.headers["location"].rstrip("/").endswith("help") or resp.headers["location"].endswith("/help/")
resp = tc.get("/help")
assert b'name="description"' in resp.content
assert b'name="robots"' in resp.content
def test_help_page_has_structured_data(self):
"""Verify JSON-LD structured data is present."""
from fastapi import FastAPI
from fastapi.testclient import TestClient
from starlette.middleware.sessions import SessionMiddleware
from app.views.help import router
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="test-secret")
app.include_router(router)
with TestClient(app) as tc:
resp = tc.get("/help")
assert b"application/ld+json" in resp.content
assert b"FAQPage" in resp.content
def test_docs_build_path_constant(self):
"""Verify _DOCS_BUILD_DIR is resolved relative to the app package."""
@@ -71,6 +109,38 @@ class TestHelpViewUnit:
# Should point to <repo_root>/docs_build
assert _DOCS_BUILD_DIR.name == "docs_build"
def test_zammad_chat_hidden_when_disabled(self):
"""Chat widget markup should not appear when Zammad chat is disabled."""
from fastapi import FastAPI
from fastapi.testclient import TestClient
from starlette.middleware.sessions import SessionMiddleware
from app.views.help import router
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="test-secret")
app.include_router(router)
with TestClient(app) as tc:
resp = tc.get("/help")
# Default settings: zammad_chat_enabled=False → no chat script
assert b"ZammadChat" not in resp.content
def test_zammad_form_hidden_when_disabled(self):
"""Ticket form markup should not appear when Zammad form is disabled."""
from fastapi import FastAPI
from fastapi.testclient import TestClient
from starlette.middleware.sessions import SessionMiddleware
from app.views.help import router
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="test-secret")
app.include_router(router)
with TestClient(app) as tc:
resp = tc.get("/help")
# Default settings: zammad_form_enabled=False → no form script
assert b"ZammadForm" not in resp.content
@pytest.mark.integration
class TestHelpNavigationLink:
@@ -81,12 +151,29 @@ class TestHelpNavigationLink:
response = client.get("/about")
assert response.status_code == 200
# The Help link should be present somewhere in the rendered page
assert b"/help/" in response.content
assert b"/help" in response.content
def test_help_link_has_accessible_text(self, client):
"""The Help link should have visible text for accessibility."""
response = client.get("/about")
assert response.status_code == 200
content = response.text
# Should include the word "Help" associated with /help/
# Should include the word "Help" associated with /help
assert "Help" in content
@pytest.mark.integration
class TestAdminDocsLinks:
"""Tests that admin doc links are in the admin menu markup."""
def test_api_docs_link_in_admin_menu(self, client):
"""The admin menu HTML should contain the API Docs link."""
response = client.get("/about")
assert response.status_code == 200
assert b"/admin/api-docs" in response.content
def test_developer_docs_link_in_admin_menu(self, client):
"""The admin menu HTML should contain the Developer Docs link."""
response = client.get("/about")
assert response.status_code == 200
assert b"/developer-docs/" in response.content