docs: document server-side rendering pipeline, response timing middleware, and Telegram pairing API
- Add comprehensive documentation for backend content rendering in AGENTS.md, detailing the new `render_content` and `render_title` Jinja globals built on mistune with media processing, emoji shortcodes, and XSS protection
- Document the `X-Response-Time` header and bottom-left render time indicator in README.md
- Update bot token pricing documentation to clarify fallback vs gateway cost headers
- Add `email_accounts` to soft-delete tables and `idx_users_role` composite index in database schema
- Implement `telegram_pairings` and `telegram_links` table creation with column migration and indexes
- Add `/profile/{username}/telegram` endpoint to docs API with request/unpair actions
- Register `TelegramService` in main.py lifespan and add `response_timing` middleware emitting `X-Response-Time` header
- Introduce `TelegramPairForm` model and `guard_public_host_sync` synchronous host validation function
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import asyncio
|
||||
|
||||
from devplacepy.services.telegram.backend import FakeTelegramBackend
|
||||
from devplacepy.services.telegram.worker import TelegramWorker
|
||||
|
||||
|
||||
def _run(coro):
|
||||
return asyncio.new_event_loop().run_until_complete(coro)
|
||||
|
||||
|
||||
def _worker(backend):
|
||||
frames = []
|
||||
|
||||
async def emit(frame):
|
||||
frames.append(frame)
|
||||
|
||||
return TelegramWorker(backend, emit), frames
|
||||
|
||||
|
||||
def test_private_text_message_emitted():
|
||||
backend = FakeTelegramBackend()
|
||||
worker, frames = _worker(backend)
|
||||
_run(
|
||||
worker.process_update(
|
||||
{
|
||||
"update_id": 1,
|
||||
"message": {
|
||||
"chat": {"id": 42, "type": "private"},
|
||||
"from": {"id": 7},
|
||||
"text": "hello",
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
messages = [f for f in frames if f["type"] == "message"]
|
||||
assert len(messages) == 1
|
||||
assert messages[0]["chat_id"] == 42 and messages[0]["text"] == "hello"
|
||||
|
||||
|
||||
def test_group_message_ignored():
|
||||
backend = FakeTelegramBackend()
|
||||
worker, frames = _worker(backend)
|
||||
_run(
|
||||
worker.process_update(
|
||||
{
|
||||
"update_id": 1,
|
||||
"message": {
|
||||
"chat": {"id": 9, "type": "group"},
|
||||
"from": {"id": 7},
|
||||
"text": "x",
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
assert not [f for f in frames if f["type"] == "message"]
|
||||
|
||||
|
||||
def test_photo_becomes_data_uri():
|
||||
backend = FakeTelegramBackend()
|
||||
backend.files["abc"] = b"\xff\xd8\xff" + b"0" * 50
|
||||
worker, frames = _worker(backend)
|
||||
_run(
|
||||
worker.process_update(
|
||||
{
|
||||
"update_id": 1,
|
||||
"message": {
|
||||
"chat": {"id": 42, "type": "private"},
|
||||
"from": {"id": 7},
|
||||
"photo": [{"file_id": "abc", "file_size": 53}],
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
message = [f for f in frames if f["type"] == "message"][0]
|
||||
assert message["images"][0].startswith("data:image/jpeg;base64,")
|
||||
|
||||
|
||||
def test_html_send_falls_back_to_plain_on_entity_error():
|
||||
class EntityBackend(FakeTelegramBackend):
|
||||
async def send_message(self, chat_id, text, parse_mode):
|
||||
if parse_mode:
|
||||
return {
|
||||
"ok": False,
|
||||
"error_code": 400,
|
||||
"description": "Bad Request: can't parse entities",
|
||||
}
|
||||
return await super().send_message(chat_id, text, None)
|
||||
|
||||
backend = EntityBackend()
|
||||
worker, frames = _worker(backend)
|
||||
_run(
|
||||
worker.handle_command(
|
||||
{"cmd": "send", "req_id": "r1", "chat_id": 42, "text": "<b>x</b>", "parse_mode": "HTML"}
|
||||
)
|
||||
)
|
||||
assert backend.sent[-1]["parse_mode"] is None
|
||||
result = [f for f in frames if f["type"] == "result"][0]
|
||||
assert result["ok"] and result["req_id"] == "r1"
|
||||
Reference in New Issue
Block a user