forked from retoor/devplacepy
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,243 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
import devplacepy.main as m
|
||||
from devplacepy.database import get_table, init_db, set_setting
|
||||
from devplacepy.services.gitea import runtime, store
|
||||
from devplacepy.services.gitea.fake import FakeGiteaClient
|
||||
from devplacepy.utils import create_session, make_combined_slug
|
||||
from tests.conftest import run_async
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _init_db_components_title():
|
||||
init_db()
|
||||
yield
|
||||
|
||||
|
||||
def _client():
|
||||
return TestClient(m.app)
|
||||
|
||||
|
||||
def _owner():
|
||||
uid = f"dptitle-{uuid4().hex[:10]}"
|
||||
get_table("users").insert(
|
||||
{
|
||||
"uid": uid,
|
||||
"username": f"dpt_{uid[-6:]}",
|
||||
"email": f"{uid[-6:]}@title.test",
|
||||
"password_hash": "x",
|
||||
"role": "Member",
|
||||
"is_active": True,
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
)
|
||||
return uid
|
||||
|
||||
|
||||
def _seed_post(title):
|
||||
owner = _owner()
|
||||
uid = str(uuid4())
|
||||
slug = make_combined_slug(title, uid)
|
||||
get_table("posts").insert(
|
||||
{
|
||||
"deleted_at": None,
|
||||
"deleted_by": None,
|
||||
"uid": uid,
|
||||
"user_uid": owner,
|
||||
"slug": slug,
|
||||
"title": title,
|
||||
"content": "Body text for the dp-title post.",
|
||||
"topic": "general",
|
||||
"project_uid": None,
|
||||
"image": None,
|
||||
"stars": 0,
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
)
|
||||
return slug
|
||||
|
||||
|
||||
def _seed_project(title):
|
||||
owner = _owner()
|
||||
uid = str(uuid4())
|
||||
slug = make_combined_slug(title, uid)
|
||||
get_table("projects").insert(
|
||||
{
|
||||
"deleted_at": None,
|
||||
"deleted_by": None,
|
||||
"uid": uid,
|
||||
"user_uid": owner,
|
||||
"slug": slug,
|
||||
"title": title,
|
||||
"description": "Project description for the dp-title test.",
|
||||
"project_type": "software",
|
||||
"platforms": "Linux",
|
||||
"status": "Released",
|
||||
"stars": 0,
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
)
|
||||
return slug
|
||||
|
||||
|
||||
def _seed_gist(title):
|
||||
owner = _owner()
|
||||
uid = str(uuid4())
|
||||
slug = make_combined_slug(title, uid)
|
||||
get_table("gists").insert(
|
||||
{
|
||||
"deleted_at": None,
|
||||
"deleted_by": None,
|
||||
"uid": uid,
|
||||
"user_uid": owner,
|
||||
"slug": slug,
|
||||
"title": title,
|
||||
"description": "Gist description for the dp-title test.",
|
||||
"source_code": "print('title')",
|
||||
"language": "python",
|
||||
"stars": 0,
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
)
|
||||
return slug
|
||||
|
||||
|
||||
def _seed_news(title):
|
||||
uid = str(uuid4())
|
||||
slug = make_combined_slug(title, uid)
|
||||
get_table("news").insert(
|
||||
{
|
||||
"deleted_at": None,
|
||||
"deleted_by": None,
|
||||
"uid": uid,
|
||||
"slug": slug,
|
||||
"title": title,
|
||||
"description": "News description for the dp-title test.",
|
||||
"content": "Body content for the seeded article.",
|
||||
"url": "https://example.com/article",
|
||||
"source_name": "ExampleSource",
|
||||
"status": "published",
|
||||
"synced_at": datetime.now(timezone.utc).isoformat(),
|
||||
"show_on_landing": 1,
|
||||
"grade": 8,
|
||||
}
|
||||
)
|
||||
return slug
|
||||
|
||||
|
||||
def test_post_detail_renders_title_backend():
|
||||
title = "DpTitle Post Alpha"
|
||||
slug = _seed_post(title)
|
||||
html = _client().get(f"/posts/{slug}").text
|
||||
assert f'<h1 class="post-detail-title">{title}</h1>' in html
|
||||
assert "<dp-title" not in html
|
||||
|
||||
|
||||
def test_project_detail_and_listing_render_title_backend():
|
||||
title = "DpTitle Project Beta"
|
||||
slug = _seed_project(title)
|
||||
client = _client()
|
||||
detail = client.get(f"/projects/{slug}").text
|
||||
assert f'<h1 class="project-detail-title">{title}</h1>' in detail
|
||||
listing = client.get("/projects").text
|
||||
assert f'<h3 class="project-card-title">{title}</h3>' in listing
|
||||
assert "<dp-title" not in detail and "<dp-title" not in listing
|
||||
|
||||
|
||||
def test_gist_detail_and_listing_render_title_backend():
|
||||
title = "DpTitle Gist Gamma"
|
||||
slug = _seed_gist(title)
|
||||
client = _client()
|
||||
detail = client.get(f"/gists/{slug}").text
|
||||
assert f'<h1 class="gist-detail-title">{title}</h1>' in detail
|
||||
listing = client.get("/gists").text
|
||||
assert f'<h3 class="gist-card-title">{title}</h3>' in listing
|
||||
assert "<dp-title" not in detail and "<dp-title" not in listing
|
||||
|
||||
|
||||
def test_news_detail_and_listing_render_title_backend():
|
||||
title = "DpTitle News Delta"
|
||||
slug = _seed_news(title)
|
||||
client = _client()
|
||||
detail = client.get(f"/news/{slug}").text
|
||||
assert f'<h1 class="news-detail-title">{title}</h1>' in detail
|
||||
listing = client.get("/news").text
|
||||
assert "<dp-title" not in detail and "<dp-title" not in listing
|
||||
|
||||
|
||||
def test_feed_listing_renders_post_title_backend():
|
||||
title = "DpTitle Feed Epsilon"
|
||||
_seed_post(title)
|
||||
html = _client().get("/feed").text
|
||||
assert f'<h3 class="post-title">{title}</h3>' in html
|
||||
assert "<dp-title" not in html
|
||||
|
||||
|
||||
def _gitea_on():
|
||||
runtime.set_client(FakeGiteaClient())
|
||||
set_setting("gitea_base_url", "https://gitea.test")
|
||||
set_setting("gitea_owner", "retoor")
|
||||
set_setting("gitea_repo", "pydevplace")
|
||||
set_setting("gitea_token", "test-token")
|
||||
set_setting("issue_ai_enhance", "0")
|
||||
|
||||
|
||||
def _gitea_off():
|
||||
runtime.set_client(None)
|
||||
for key in ("gitea_base_url", "gitea_owner", "gitea_repo", "gitea_token"):
|
||||
set_setting(key, "")
|
||||
|
||||
|
||||
def test_issue_detail_and_listing_render_title_backend():
|
||||
_gitea_on()
|
||||
try:
|
||||
fake = runtime.get_client()
|
||||
title = "DpTitle Issue Zeta"
|
||||
owner = _owner()
|
||||
issue = run_async(fake.create_issue(title, "issue details for the title test"))
|
||||
number = int(issue["number"])
|
||||
store.record_ticket(
|
||||
number=number,
|
||||
author_uid=owner,
|
||||
original_title=title,
|
||||
original_description="issue details for the title test",
|
||||
enhanced_title=title,
|
||||
html_url=issue.get("html_url", ""),
|
||||
status="open",
|
||||
)
|
||||
client = _client()
|
||||
client.cookies.set("session", create_session(owner))
|
||||
detail = client.get(f"/issues/{number}").text
|
||||
assert f'<h1 class="issue-detail-title">{title}</h1>' in detail
|
||||
listing = client.get("/issues").text
|
||||
assert "<dp-title" not in detail and "<dp-title" not in listing
|
||||
finally:
|
||||
_gitea_off()
|
||||
|
||||
|
||||
def test_post_content_rendered_backend_with_media():
|
||||
owner = _owner()
|
||||
from uuid import uuid4
|
||||
from devplacepy.database import get_table as _gt
|
||||
uid = str(uuid4())
|
||||
slug = make_combined_slug("Content Render Check", uid)
|
||||
body = "be **bold** :rocket: watch https://youtu.be/dQw4w9WgXcQ"
|
||||
_gt("posts").insert(
|
||||
{
|
||||
"deleted_at": None, "deleted_by": None, "uid": uid, "user_uid": owner,
|
||||
"slug": slug, "title": "Content Render Check", "content": body,
|
||||
"topic": "general", "project_uid": None, "image": None, "stars": 0,
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
)
|
||||
html = _client().get(f"/posts/{slug}").text
|
||||
assert "<strong>bold</strong>" in html
|
||||
assert "\U0001F680" in html
|
||||
assert 'class="embed-youtube"' in html
|
||||
assert "data-render" not in html.split('class="post-detail-content')[1][:200]
|
||||
Reference in New Issue
Block a user