Update
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]
|
||||
@@ -0,0 +1,86 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
from tests.conftest import BASE_URL
|
||||
|
||||
_counter = [0]
|
||||
JSON = {"Accept": "application/json"}
|
||||
|
||||
|
||||
def _signup(password="secret123"):
|
||||
_counter[0] += 1
|
||||
name = f"tgpair{int(time.time() * 1000)}{_counter[0]}"
|
||||
session = requests.Session()
|
||||
session.post(
|
||||
f"{BASE_URL}/auth/signup",
|
||||
data={
|
||||
"username": name,
|
||||
"email": f"{name}@t.dev",
|
||||
"password": password,
|
||||
"confirm_password": password,
|
||||
},
|
||||
allow_redirects=True,
|
||||
)
|
||||
return session, name
|
||||
|
||||
|
||||
def test_owner_requests_pairing_code(app_server):
|
||||
session, name = _signup()
|
||||
resp = session.post(
|
||||
f"{BASE_URL}/profile/{name}/telegram",
|
||||
data={"action": "request"},
|
||||
headers=JSON,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert body["ok"] is True
|
||||
assert body["code"].isdigit() and len(body["code"]) == 4
|
||||
assert body["ttl_minutes"] >= 1
|
||||
|
||||
|
||||
def test_unpair_returns_ok(app_server):
|
||||
session, name = _signup()
|
||||
resp = session.post(
|
||||
f"{BASE_URL}/profile/{name}/telegram",
|
||||
data={"action": "unpair"},
|
||||
headers=JSON,
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["paired"] is False
|
||||
|
||||
|
||||
def test_other_user_cannot_request_code(app_server):
|
||||
_, target = _signup()
|
||||
attacker, _ = _signup()
|
||||
resp = attacker.post(
|
||||
f"{BASE_URL}/profile/{target}/telegram",
|
||||
data={"action": "request"},
|
||||
headers=JSON,
|
||||
allow_redirects=False,
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
def test_request_without_json_redirects_to_profile(app_server):
|
||||
session, name = _signup()
|
||||
resp = session.post(
|
||||
f"{BASE_URL}/profile/{name}/telegram",
|
||||
data={"action": "request"},
|
||||
allow_redirects=False,
|
||||
)
|
||||
assert resp.status_code == 302
|
||||
assert resp.headers["location"] == f"/profile/{name}"
|
||||
|
||||
|
||||
def test_unpair_without_json_redirects_to_profile(app_server):
|
||||
session, name = _signup()
|
||||
resp = session.post(
|
||||
f"{BASE_URL}/profile/{name}/telegram",
|
||||
data={"action": "unpair"},
|
||||
allow_redirects=False,
|
||||
)
|
||||
assert resp.status_code == 302
|
||||
assert resp.headers["location"] == f"/profile/{name}"
|
||||
@@ -1,5 +1,7 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import re
|
||||
|
||||
import devplacepy.main as m
|
||||
from starlette.testclient import TestClient
|
||||
def _patch_limits(monkeypatch, limit, window=60):
|
||||
@@ -43,3 +45,29 @@ def test_rate_limit_reads_configured_setting(monkeypatch):
|
||||
second = client.post("/", headers={"X-Real-IP": "7.7.7.7"}).status_code
|
||||
assert first != 429
|
||||
assert second == 429
|
||||
|
||||
|
||||
def test_response_time_header_present():
|
||||
client = TestClient(m.app)
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200
|
||||
value = response.headers.get("X-Response-Time")
|
||||
assert value is not None and value.endswith("ms")
|
||||
assert float(value[:-2]) >= 0
|
||||
|
||||
|
||||
def test_response_time_indicator_rendered_on_html():
|
||||
client = TestClient(m.app)
|
||||
match = re.search(
|
||||
r'<div class="response-time-indicator"[^>]*>([\d.]+) ms</div>',
|
||||
client.get("/").text,
|
||||
)
|
||||
assert match is not None
|
||||
assert float(match.group(1)) >= 0
|
||||
|
||||
|
||||
def test_response_time_indicator_absent_from_json():
|
||||
client = TestClient(m.app)
|
||||
response = client.get("/feed", headers={"Accept": "application/json"})
|
||||
assert "response-time-indicator" not in response.text
|
||||
assert response.headers.get("X-Response-Time", "").endswith("ms")
|
||||
|
||||
Reference in New Issue
Block a user