diff --git a/post_parser.py b/post_parser.py
index 32ca6f1..c370e7a 100644
--- a/post_parser.py
+++ b/post_parser.py
@@ -486,7 +486,10 @@ class PostParser:
html_content = []
if debug:
- html_content.append(f'
Title: {data["html"]["title"]}
')
+ # title comes from _generate_title (user-controlled post text) and never goes
+ # through bleach — escape it before embedding, same as raw_message below.
+ title_escaped = html.escape(str(data["html"]["title"]))
+ html_content.append(f'Title: {title_escaped}
')
html_content.append(f'{data["html"]["body"]}
')
html_content.append(f'')
diff --git a/tests/pytest.ini b/pytest.ini
similarity index 86%
rename from tests/pytest.ini
rename to pytest.ini
index 6d6f517..d6e331d 100644
--- a/tests/pytest.ini
+++ b/pytest.ini
@@ -1,8 +1,9 @@
[pytest]
+testpaths = tests
addopts = -ra
# The stage-1 anti-hang tests are async; run them without per-test event loops
# being set up by hand. (The tests also carry @pytest.mark.asyncio explicitly, so
# they work in strict mode too — but auto keeps the plugin's requirement obvious.)
asyncio_mode = auto
filterwarnings =
- ignore::DeprecationWarning
\ No newline at end of file
+ ignore::DeprecationWarning
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..850fb53
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,17 @@
+"""Shared test bootstrap (issue #17).
+
+Ensure the repo root is importable and install the config mock BEFORE any test
+module imports application code. Pytest imports conftest.py before collecting
+test modules, so doing this here (instead of a per-module preamble) makes the
+suite order-independent regardless of collection order or invocation directory.
+"""
+import os
+import sys
+
+ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+if ROOT not in sys.path:
+ sys.path.insert(0, ROOT)
+
+import tests.mock_config as _mock_config
+
+sys.modules['config'] = _mock_config
diff --git a/tests/test_postparser_author_info.py b/tests/test_postparser_author_info.py
index 84bb48e..2b9c314 100644
--- a/tests/test_postparser_author_info.py
+++ b/tests/test_postparser_author_info.py
@@ -7,13 +7,6 @@
import unittest
from unittest.mock import MagicMock
-import sys
-import os
-# Add project root to sys.path to find post_parser
-sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-
-# Mock the config module
-sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
from pyrogram.types import Message
from post_parser import PostParser
diff --git a/tests/test_postparser_extract_flags.py b/tests/test_postparser_extract_flags.py
index a6ab03f..c450e1e 100644
--- a/tests/test_postparser_extract_flags.py
+++ b/tests/test_postparser_extract_flags.py
@@ -7,14 +7,6 @@
import unittest
from unittest.mock import MagicMock, PropertyMock
-import sys
-import os
-
-# Add project root to sys.path to find post_parser
-sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-
-# Mock the config module before importing PostParser
-sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
from pyrogram.types import Message, Chat, Reaction, MessageReactions
from pyrogram.enums import MessageMediaType
diff --git a/tests/test_postparser_gen_title.py b/tests/test_postparser_gen_title.py
index 6ebda97..08315eb 100644
--- a/tests/test_postparser_gen_title.py
+++ b/tests/test_postparser_gen_title.py
@@ -7,13 +7,6 @@
# pylance: disable=reportMissingImports, reportMissingModuleSource
import unittest
from unittest.mock import MagicMock, PropertyMock
-import sys
-import os
-# Add project root to sys.path to find post_parser
-sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-
-# Mock the config module
-sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
from pyrogram.types import Message, Document
from pyrogram.enums import MessageMediaType
diff --git a/tests/test_stage1_hangs.py b/tests/test_stage1_hangs.py
index a1b3104..3b9a076 100644
--- a/tests/test_stage1_hangs.py
+++ b/tests/test_stage1_hangs.py
@@ -10,18 +10,12 @@ Stage 1 (anti-hang) regression tests:
worker, and task_done stays balanced so queue.join() completes.
- Gate cancellation during the spacing wait does not lose the permit.
"""
-import os
-import sys
import time
import asyncio
from types import SimpleNamespace
import pytest
-# Add project root to sys.path and mock the config module (same pattern as the other tests).
-sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
-
import tg_throttle
import tg_cache
from pyrogram import errors
diff --git a/tests/test_stage2_static.py b/tests/test_stage2_static.py
index a8a6538..00ce557 100644
--- a/tests/test_stage2_static.py
+++ b/tests/test_stage2_static.py
@@ -18,17 +18,12 @@ Covers the four scenarios from the plan plus the subtle in-flight-dedup lifecycl
fresh partials and non-temp files.
"""
import os
-import sys
import time
import asyncio
from types import SimpleNamespace
import pytest
-# Add project root to sys.path and mock the config module (same pattern as the other tests).
-sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
-
import api_server
from pyrogram import errors
from pyrogram.enums import MessageMediaType
diff --git a/tests/test_stage3_fileresponse.py b/tests/test_stage3_fileresponse.py
index 9dc054c..9e4f4ef 100644
--- a/tests/test_stage3_fileresponse.py
+++ b/tests/test_stage3_fileresponse.py
@@ -36,16 +36,11 @@ All 206 responses that carry data return byte-for-byte identical slices old vs n
real regression is hidden behind an "accepted difference".
"""
import os
-import sys
import time
import logging
import pytest
-# Add project root to sys.path and mock the config module (same pattern as the other tests).
-sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-sys.modules['config'] = __import__('tests.mock_config', fromlist=['get_settings'])
-
from fastapi import FastAPI, Request
from fastapi.testclient import TestClient
diff --git a/tests/test_stage4_eventloop.py b/tests/test_stage4_eventloop.py
index c0648ec..0ef5c49 100644
--- a/tests/test_stage4_eventloop.py
+++ b/tests/test_stage4_eventloop.py
@@ -16,9 +16,7 @@ Covers:
- 4.4 sanitize coverage (XSS): a "
+ # verbatim — it must be html-escaped before being embedded in the debug output.
+ msg = make_message(25, media=MessageMediaType.POLL)
+ msg.poll = SimpleNamespace(question="")
+ parser = PostParser(_make_client_returning(msg))
+ html_out = await parser.get_post("testchan", 25, "html", debug=True)
+
+ # No live