1- """Unit tests for POST /api/generate-pdf (api/pdf.py)."""
1+ """Unit tests for POST /api/generate-pdf (api/pdf.py). Closes #72. """
22
33from __future__ import annotations
44
5+ from typing import Any
56from unittest .mock import patch
67
78PDF_MAGIC = b"%PDF-"
89
910
10- def _post_pdf (client , * , markdown : str = "" , title : str = "Chat" , json_data = None ):
11+ def _post_pdf (
12+ client ,
13+ * ,
14+ markdown : str = "" ,
15+ title : str = "Chat" ,
16+ json_data : dict [str , Any ] | None = None ,
17+ ):
1118 if json_data is not None :
1219 return client .post (
1320 "/api/generate-pdf" ,
@@ -32,7 +39,7 @@ def _assert_pdf_response(response) -> None:
3239
3340
3441class TestGeneratePdfHappyPath :
35- def test_normal_conversation_markdown (self , client ):
42+ def test_normal_conversation_markdown (self , pdf_client ):
3643 md = """# Chat export
3744
3845## User question
@@ -49,50 +56,70 @@ def fact(n):
4956
5057---
5158"""
52- response = _post_pdf (client , markdown = md , title = "Happy conversation" )
59+ response = _post_pdf (pdf_client , markdown = md , title = "Happy conversation" )
5360 _assert_pdf_response (response )
5461 assert (
5562 'attachment; filename="Happy conversation.pdf"'
5663 in response .headers .get ("Content-Disposition" , "" )
5764 )
5865
66+ def test_empty_json_body_uses_defaults (self , pdf_client ):
67+ response = _post_pdf (pdf_client , json_data = {})
68+ _assert_pdf_response (response )
69+ assert (
70+ 'attachment; filename="Chat.pdf"'
71+ in response .headers .get ("Content-Disposition" , "" )
72+ )
73+
74+ def test_unsafe_title_characters_sanitized_in_filename (self , pdf_client ):
75+ response = _post_pdf (
76+ pdf_client ,
77+ markdown = "Hello" ,
78+ title = 'bad<>:"/\\ |?*name' ,
79+ )
80+ _assert_pdf_response (response )
81+ assert (
82+ 'attachment; filename="bad_________name.pdf"'
83+ in response .headers .get ("Content-Disposition" , "" )
84+ )
85+
5986
6087class TestGeneratePdfEdgeCases :
61- def test_empty_markdown (self , client ):
62- response = _post_pdf (client , markdown = "" , title = "Empty chat" )
88+ def test_empty_markdown (self , pdf_client ):
89+ response = _post_pdf (pdf_client , markdown = "" , title = "Empty chat" )
6390 _assert_pdf_response (response )
6491
65- def test_very_long_content (self , client ):
92+ def test_very_long_content (self , pdf_client ):
6693 line = "This is a repeated paragraph for length testing. " * 20
6794 md = "\n " .join (f"Line { i } : { line } " for i in range (500 ))
68- response = _post_pdf (client , markdown = md , title = "Long chat" )
95+ response = _post_pdf (pdf_client , markdown = md , title = "Long chat" )
6996 _assert_pdf_response (response )
7097
71- def test_unicode_and_emoji_content (self , client ):
98+ def test_unicode_and_emoji_content (self , pdf_client ):
7299 md = (
73100 "Smart quotes: “hello” and ’world’\n "
74101 "Emoji: 🚀🔥 should not break PDF\n "
75102 "Bullet • point\n "
76103 )
77- response = _post_pdf (client , markdown = md , title = "Unicode chat" )
104+ response = _post_pdf (pdf_client , markdown = md , title = "Unicode chat" )
78105 _assert_pdf_response (response )
79106
80107
81108class TestGeneratePdfErrors :
82- def test_pdf_engine_failure_returns_500 (self , client ):
109+ def test_pdf_engine_failure_returns_500 (self , pdf_client ):
83110 with patch (
84111 "fpdf.fpdf.FPDF.output" ,
85112 side_effect = RuntimeError ("simulated failure" ),
86113 ):
87- response = _post_pdf (client , markdown = "Hello" , title = "Fail" )
114+ response = _post_pdf (pdf_client , markdown = "Hello" , title = "Fail" )
88115 assert response .status_code == 500
89116 assert response .get_json () == {"error" : "Failed to generate PDF" }
90117
91- def test_invalid_export_payload_returns_500 (self , client ):
118+ def test_invalid_export_payload_returns_500 (self , pdf_client ):
92119 # Conversation IDs are resolved client-side (tabs API) before markdown is
93120 # POSTed here. A non-string markdown field mimics a corrupted export request.
94121 response = _post_pdf (
95- client ,
122+ pdf_client ,
96123 json_data = {"markdown" : ["not" , "a" , "string" ], "title" : "Bad payload" },
97124 )
98125 assert response .status_code == 500
0 commit comments