# retoor <retoor@molodetz.nl>
import types
import devplacepy.seo as seo
import devplacepy.attachments as attach
def _seo_request():
return types.SimpleNamespace(
base_url="https://x.test/",
url=types.SimpleNamespace(path="/posts/abc"),
query_params={},
)
def test_truncate_no_overflow():
assert len(seo.truncate("x" * 200)) <= 160
assert seo.truncate("short text") == "short text"
assert seo.truncate("") == ""
def test_software_application_url_is_per_project():
schema = seo.software_application_schema(
{"uid": "p1", "slug": "p1-foo", "title": "P"}, "https://x.test"
)
assert schema["url"] == "https://x.test/projects/p1-foo"
def test_schema_types():
assert seo.organization_schema("https://x.test")["@type"] == "Organization"
assert (
seo.news_article_schema(
{"uid": "n1", "title": "T", "synced_at": "2026-01-01"}, "https://x.test"
)["@type"]
== "NewsArticle"
)
assert (
seo.software_source_code_schema(
{"uid": "g1", "title": "G", "language": "python"}, "https://x.test"
)["@type"]
== "SoftwareSourceCode"
)
def test_news_article_publisher_is_organization():
schema = seo.news_article_schema(
{"uid": "n1", "title": "T", "synced_at": "2026-01-01"}, "https://x.test"
)
assert schema["publisher"]["@type"] == "Organization"
def test_project_schema_type_mapping():
assert seo.project_schema_type("game") == "VideoGame"
assert seo.project_schema_type("game_asset") == "CreativeWork"
assert seo.project_schema_type("website") == "WebApplication"
assert seo.project_schema_type("software") == "SoftwareApplication"
assert seo.project_schema_type("mobile_app") == "SoftwareApplication"
assert seo.project_schema_type(None) == "SoftwareApplication"
assert seo.project_schema_type("unknown") == "SoftwareApplication"
def test_software_application_schema_is_type_aware():
game = seo.software_application_schema(
{"uid": "p1", "slug": "p1-g", "title": "G", "project_type": "game", "platforms": "PC, Web"},
"https://x.test",
)
assert game["@type"] == "VideoGame"
assert game["gamePlatform"] == ["PC", "Web"]
assert "applicationCategory" not in game
web = seo.software_application_schema(
{"uid": "p2", "slug": "p2-w", "title": "W", "project_type": "website"},
"https://x.test",
)
assert web["@type"] == "WebApplication"
assert web["browserRequirements"] == "Requires JavaScript"
assert web["applicationCategory"] == "DeveloperApplication"
def test_software_application_schema_rating_and_image():
project = {"uid": "p1", "slug": "p1-s", "title": "S", "project_type": "software"}
plain = seo.software_application_schema(project, "https://x.test")
assert "aggregateRating" not in plain
assert "image" not in plain
rich = seo.software_application_schema(
project,
"https://x.test",
image_url="https://x.test/img.png",
star_count=7,
comment_count=3,
)
assert rich["aggregateRating"]["ratingCount"] == "7"
assert rich["image"] == "https://x.test/img.png"
assert rich["interactionStatistic"]["userInteractionCount"] == 3
def test_software_application_schema_website_and_screenshots():
project = {
"uid": "p1",
"slug": "p1-s",
"title": "S",
"project_type": "software",
"website_url": "https://myproject.dev",
}
schema = seo.software_application_schema(
project,
"https://x.test",
screenshot_urls=["https://x.test/a.png", "https://x.test/b.png"],
)
assert schema["sameAs"] == ["https://myproject.dev"]
assert schema["screenshot"] == ["https://x.test/a.png", "https://x.test/b.png"]
bare = seo.software_application_schema(
{"uid": "p2", "slug": "p2-s", "title": "B"}, "https://x.test"
)
assert "sameAs" not in bare
assert "screenshot" not in bare
def test_project_devlog_schema_builds_blog_graph():
project = {"uid": "p1", "slug": "p1-s", "title": "Nebula"}
assert seo.project_devlog_schema(project, [], "https://x.test") is None
posts = [
{
"post": {
"uid": "a1",
"slug": "a1-first",
"title": "First update",
"content": "Progress!",
"created_at": "2026-01-01T00:00:00+00:00",
},
"author": {"username": "dev"},
"comment_count": 2,
}
]
schema = seo.project_devlog_schema(project, posts, "https://x.test")
assert schema["@type"] == "Blog"
assert schema["url"] == "https://x.test/projects/p1-s#devlog"
entry = schema["blogPost"][0]
assert entry["@type"] == "BlogPosting"
assert entry["headline"] == "First update"
assert entry["url"] == "https://x.test/posts/a1-first"
assert entry["author"]["name"] == "dev"
assert entry["commentCount"] == 2
def test_site_url_precedence(monkeypatch):
import devplacepy.database as database
monkeypatch.setattr(seo, "SITE_URL", "https://constant.test")
monkeypatch.setattr(
database, "get_setting", lambda *a, **k: "https://setting.test"
)
assert seo.site_url(None) == "https://setting.test"
monkeypatch.setattr(database, "get_setting", lambda *a, **k: "")
assert seo.site_url(None) == "https://constant.test"
monkeypatch.setattr(seo, "SITE_URL", "")
request = types.SimpleNamespace(base_url="https://req.test/")
assert seo.site_url(request) == "https://req.test"
def test_site_url_falls_back_to_request(monkeypatch):
monkeypatch.setattr(seo, "SITE_URL", "")
request = types.SimpleNamespace(base_url="http://fallback.test/")
assert seo.site_url(request) == "http://fallback.test"
def test_upload_allowlist_excludes_dangerous_types():
assert ".svg" not in attach.ALLOWED_UPLOAD_TYPES
assert ".html" not in attach.ALLOWED_UPLOAD_TYPES
assert ".png" in attach.ALLOWED_UPLOAD_TYPES
assert ".svg" not in attach.POST_IMAGE_EXTENSIONS
def test_detect_mime_neutralizes_dangerous_types():
assert attach._detect_mime(b"", "x.svg") == "application/octet-stream"
assert attach._detect_mime(b"", "x.html") == "application/octet-stream"
assert attach._detect_mime(b"", "x.png") == "image/png"
def test_base_seo_context_emits_keywords_key():
ctx = seo.base_seo_context(
_seo_request(),
title="A Post",
description="Body text",
keywords="python, sqlite",
)
assert "meta_keywords" in ctx
assert ctx["meta_keywords"] == "python, sqlite"
def test_base_seo_context_default_keywords_empty_without_target():
ctx = seo.base_seo_context(
_seo_request(), title="A Post", description="Body text"
)
assert ctx["meta_keywords"] == ""
def test_base_seo_context_description_strips_markdown(monkeypatch):
monkeypatch.setattr(seo, "_ready_seo_metadata", lambda target: None)
ctx = seo.base_seo_context(
_seo_request(),
title="A Post",
description="## Heading\n\nThis is **bold** body with `code`.",
)
assert "##" not in ctx["meta_description"]
assert "**" not in ctx["meta_description"]
assert "`" not in ctx["meta_description"]
assert "bold" in ctx["meta_description"]
def test_base_seo_context_plain_default_fills_fields_for_target(monkeypatch):
monkeypatch.setattr(seo, "_ready_seo_metadata", lambda target: None)
ctx = seo.base_seo_context(
_seo_request(),
title="Async Python Database Tooling",
description="A **guide** to async python database tooling with sqlite.",
seo_target=("post", "abc"),
)
assert ctx["meta_keywords"]
assert "python" in ctx["meta_keywords"]
assert ctx["meta_description"]
assert "**" not in ctx["meta_description"]
def test_base_seo_context_consumes_ready_metadata(monkeypatch):
monkeypatch.setattr(
seo,
"_ready_seo_metadata",
lambda target: {
"seo_title": "Generated Title",
"seo_description": "Generated clean description",
"seo_keywords": "generated, metadata",
},
)
ctx = seo.base_seo_context(
_seo_request(),
title="Raw **markdown** title",
description="raw markdown body",
seo_target=("post", "abc"),
)
assert "Generated Title" in ctx["page_title"]
assert ctx["meta_description"] == "Generated clean description"
assert ctx["meta_keywords"] == "generated, metadata"