Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

134 lines
4.4 KiB
Python

"""End-to-end rendering test for the PDF export endpoint.
Drives POST /api/v1/research/<id>/export/pdf — the endpoint the UI's
"Download PDF" button calls — through the real exporter chain
(ExporterRegistry → PDFExporter → PDFService → WeasyPrint). Only the
data layer (DB session, report assembly) is mocked, so the returned
bytes are a genuine WeasyPrint render.
Regression coverage for the digit-capture bug (#5050): listing an emoji
family in the PDF font stacks made Pango draw every digit 0-9 with Noto
Color Emoji, so numbers in exported reports rendered as wide, square
emoji glyphs ("2 0 2 6" instead of "2026"). The service-level tests in
tests/web/services/test_pdf_service.py assert the same property on
PDFService directly; this test pins it to the HTTP surface so a change
anywhere in the export chain (route, exporter registry, service) that
reintroduces the bug still fails CI.
"""
import io
from contextlib import contextmanager
from unittest.mock import MagicMock, patch
import pytest
from flask import Flask
MODULE = "local_deep_research.web.routes.research_routes"
ASSEMBLY_MOD = "local_deep_research.web.services.report_assembly_service"
# Digit-heavy markdown mirroring a real research report (dates, prices,
# token counts, citation markers) — the shapes that were unreadable.
REPORT_MARKDOWN = (
"# Pricing report 2026-07-11\n\n"
"As of July 11, 2026, plans cost $20/month and allow 44,000 tokens "
"per rolling 5-hour window [21][30].\n"
)
@pytest.fixture()
def app():
flask_app = Flask(__name__)
flask_app.secret_key = "test-secret-key"
flask_app.config["TESTING"] = True
from local_deep_research.web.routes.research_routes import research_bp
flask_app.register_blueprint(research_bp)
with patch("local_deep_research.web.auth.decorators.db_manager") as mock_db:
mock_db.is_user_connected.return_value = True
yield flask_app
@pytest.fixture()
def client(app):
return app.test_client()
@pytest.fixture(autouse=True)
def _inject_session(app):
@app.before_request
def _set_sess():
from flask import session
session["username"] = "testuser"
session["session_id"] = "sid-1"
@contextmanager
def _ctx(session):
yield session
def _digit_fonts(pdf_bytes):
"""Return the set of font names used to draw digit glyphs."""
from pdfminer.high_level import extract_pages
from pdfminer.layout import LTChar, LTTextContainer, LTTextLine
fonts = set()
for page in extract_pages(io.BytesIO(pdf_bytes)):
for element in page:
if not isinstance(element, LTTextContainer):
continue
for line in element:
if not isinstance(line, LTTextLine):
continue
for char in line:
if isinstance(char, LTChar) and char.get_text().isdigit():
fonts.add(char.fontname)
return fonts
def test_export_pdf_endpoint_renders_digits_with_text_font(client):
"""The exported PDF must draw digits with a text font, not an emoji font.
On hosts with no emoji font installed the emoji-font assertion passes
trivially, which is correct — there is no emoji font to capture the
digits.
"""
from local_deep_research.web.services.pdf_service import (
weasyprint_available,
)
if not weasyprint_available():
pytest.skip("WeasyPrint system libraries not available")
pytest.importorskip("pdfminer.high_level")
research = MagicMock()
research.id = "res-1"
research.title = "Pricing report"
research.query = "pricing"
mock_session = MagicMock()
mock_session.query.return_value.filter_by.return_value.first.return_value = research
with (
patch(f"{MODULE}.get_user_db_session", return_value=_ctx(mock_session)),
patch(
f"{ASSEMBLY_MOD}.assemble_full_report",
return_value=REPORT_MARKDOWN,
),
):
resp = client.post("/api/v1/research/res-1/export/pdf")
assert resp.status_code == 200
assert resp.content_type == "application/pdf"
assert resp.data[:4] == b"%PDF"
digit_fonts = _digit_fonts(resp.data)
assert digit_fonts, "expected the rendered PDF to contain digits"
emoji_fonts = {f for f in digit_fonts if "emoji" in f.lower()}
assert not emoji_fonts, (
f"digits in the exported PDF were rendered with an emoji font: "
f"{emoji_fonts}"
)