Build custom container images / build (map[base_image:php:8-fpm-alpine build_args:PHP_VERSION=8
context:php8-pgsql fingerprint_command:{ apk info -v | LC_ALL=C sort; find /usr/local/lib/php/extensions /usr/local/etc/php/conf.d -type f -exec sha256sum {} + | LC_ALL=C sort; }
name:ph… (push) Successful in 48s
Build custom container images / build (map[base_image:postgres:18 build_args:PG_VERSION=18
POSTGIS_VERSION=3
VCHORD_VERSION=0.5.3
context:postgres fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; find /usr/lib/postgresql -type f -exec sha256su… (push) Successful in 55s
Build custom container images / build (map[base_image:python:3 build_args:PYTHON_VERSION=3
context:python-tools fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:python-tools oci_labels:org.opencontainers.ima… (push) Successful in 54s
Build custom container images / build (map[base_image:python:3.12-slim build_args:PYTHON_VERSION=3.12-slim
context:linkki-tiedotus fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:linkki-tiedotus oci_labels:… (push) Successful in 39s
126 lines
4.4 KiB
Python
126 lines
4.4 KiB
Python
from linkki_poster.formatting import (
|
|
CAPTION_LIMIT,
|
|
estimate_caption_length,
|
|
parse_markdown_template,
|
|
render_preview_html,
|
|
render_template,
|
|
validate_markdown_syntax,
|
|
)
|
|
from linkki_poster.models import Language
|
|
|
|
|
|
def test_markdown_subset_and_placeholder_rendering() -> None:
|
|
raw = (
|
|
"# title\n"
|
|
"**bold** _italic_ ++under++ ~~strike~~ ||spoil|| `code` [site](https://example.com)\n"
|
|
"Go [English](en) and [Finnish](fi)\n"
|
|
)
|
|
parsed = parse_markdown_template(raw)
|
|
|
|
unresolved = render_template(parsed, {})
|
|
assert "<b>TITLE</b>" in unresolved
|
|
assert "<b>bold</b>" in unresolved
|
|
assert "<i>italic</i>" in unresolved
|
|
assert "<u>under</u>" in unresolved
|
|
assert "<s>strike</s>" in unresolved
|
|
assert "<tg-spoiler>spoil</tg-spoiler>" in unresolved
|
|
assert "<code>code</code>" in unresolved
|
|
assert '<a href="https://example.com">site</a>' in unresolved
|
|
assert "English" in unresolved
|
|
assert "Finnish" in unresolved
|
|
assert 'href="https://t.me/' not in unresolved
|
|
|
|
resolved = render_template(
|
|
parsed,
|
|
{
|
|
Language.EN: "https://t.me/testchan/123",
|
|
Language.FI: "https://t.me/testchan/456",
|
|
},
|
|
)
|
|
assert '<a href="https://t.me/testchan/123">English</a>' in resolved
|
|
assert '<a href="https://t.me/testchan/456">Finnish</a>' in resolved
|
|
|
|
|
|
def test_caption_length_estimate_uses_worst_case_links() -> None:
|
|
parsed = parse_markdown_template("[EN](en) " * 80)
|
|
assert estimate_caption_length(parsed, "channel") > CAPTION_LIMIT
|
|
|
|
|
|
def test_multiline_spoiler_is_supported() -> None:
|
|
parsed = parse_markdown_template("||line1\n\nline2||")
|
|
rendered = render_template(parsed, {})
|
|
assert "<tg-spoiler>line1\n\nline2</tg-spoiler>" in rendered
|
|
|
|
|
|
def test_validate_markdown_syntax_finds_unclosed_markers() -> None:
|
|
issues = validate_markdown_syntax("Can we do **[bold links](fi)")
|
|
messages = [issue.message for issue in issues]
|
|
assert any("**" in message for message in messages)
|
|
|
|
|
|
def test_lists_and_blockquotes_are_rendered_for_telegram_html() -> None:
|
|
parsed = parse_markdown_template(
|
|
"- item\n"
|
|
" - nested\n"
|
|
"1. first\n"
|
|
" 1. nested first\n"
|
|
"> quoted\n"
|
|
"> text\n"
|
|
)
|
|
rendered = render_template(parsed, {})
|
|
assert "• item" in rendered
|
|
assert "\u00a0\u00a0\u00a0\u00a0• nested" in rendered
|
|
assert "1. first" in rendered
|
|
assert "\u00a0\u00a0\u00a0\u00a01. nested first" in rendered
|
|
assert "<blockquote>quoted\ntext</blockquote>" in rendered
|
|
|
|
|
|
def test_escaped_list_markers_and_heading_markers_stay_literal() -> None:
|
|
parsed = parse_markdown_template("\\* not a list\n1\\. not numbered\n\\# not a heading")
|
|
rendered = render_template(parsed, {})
|
|
assert "* not a list" in rendered
|
|
assert "1. not numbered" in rendered
|
|
assert "# not a heading" in rendered
|
|
|
|
|
|
def test_escaped_backticks_and_link_brackets_stay_literal() -> None:
|
|
parsed = parse_markdown_template(
|
|
r"test \`code` test" "\n"
|
|
r"\```" "\n"
|
|
"code\n"
|
|
"```\n"
|
|
r"\[label\]\(url\)"
|
|
)
|
|
rendered = render_template(parsed, {})
|
|
assert r"test `code` test" in rendered
|
|
assert "<code>code</code>" not in rendered
|
|
assert "<pre><code>" not in rendered
|
|
assert "[label](url)" in rendered
|
|
|
|
|
|
def test_escaped_cross_language_link_stays_literal() -> None:
|
|
parsed = parse_markdown_template(r"\[English](en)")
|
|
rendered = render_template(parsed, {Language.EN: "https://t.me/test/123"})
|
|
assert "[English](en)" in rendered
|
|
assert 'href="https://t.me/test/123"' not in rendered
|
|
|
|
|
|
def test_preview_renders_fake_links_for_available_targets() -> None:
|
|
preview = render_preview_html("[English](en) [Finnish](fi)", {Language.EN})
|
|
assert '<a href="#en">English</a>' in preview
|
|
assert "Finnish" in preview
|
|
assert 'href="#fi"' not in preview
|
|
|
|
|
|
def test_apostrophes_are_not_escaped_in_headings_or_plain_text() -> None:
|
|
heading = render_template(parse_markdown_template("#THERE'S"), {})
|
|
plain_heading_char = render_template(parse_markdown_template("#'"), {})
|
|
bold = render_template(parse_markdown_template("**THERE'S**"), {})
|
|
|
|
assert "<b>THERE'S</b>" in heading
|
|
assert "<b>'</b>" in plain_heading_char
|
|
assert "<b>THERE'S</b>" in bold
|
|
assert "'" not in heading
|
|
assert "'" not in plain_heading_char
|
|
assert "'" not in bold
|