social media crossposting tool. 3rd time's the charm
mastodon misskey crossposting bluesky

start adding tests

zenfyr.dev eda25493 749c26db

verified
Changed files
+329
.tangled
workflows
tests
+14
.tangled/workflows/run-tests.yml
···
+
when:
+
- event: ["push", "manual"]
+
branch: ["next"]
+
+
engine: nixery
+
+
dependencies:
+
nixpkgs:
+
- uv
+
+
steps:
+
- name: run tests
+
command: |
+
uv run pytest -vv
+8
pyproject.toml
···
"requests>=2.32.5",
"websockets>=15.0.1",
]
+
+
[dependency-groups]
+
dev = [
+
"pytest>=8.4.2",
+
]
+
+
[tool.pytest.ini_options]
+
pythonpath = ["."]
+16
tests/util/html_test.py
···
+
import html
+
from util.html import HTMLToFragmentsParser
+
import cross.fragments as f
+
import pytest
+
+
@pytest.fixture()
+
def parser():
+
return HTMLToFragmentsParser()
+
+
def test_html(parser: HTMLToFragmentsParser):
+
input = "<p><del>excuse</del> <em>me</em>, <strong>test</strong> post</p><blockquote><p>very testy <a href=\"https://google.com\" target=\"_blank\" rel=\"nofollow noopener\">post</a></p></blockquote><pre><code>cat &lt;&lt; food<br></code></pre>"
+
parser.feed(input)
+
text, fragments = parser.get_result()
+
+
# TODO
+
#assert text == "~~excuse~~ *me*, **test** post\n\n> very testy post\n\n```\ncat << food\n```\n"
+161
tests/util/markdown_test.py
···
+
from util.markdown import MarkdownParser
+
import cross.fragments as f
+
import pytest
+
+
EMOJI = "🤬🤬"
+
+
+
@pytest.fixture()
+
def parser():
+
return MarkdownParser()
+
+
+
def test_empty(parser: MarkdownParser):
+
text, frgs = parser.parse("")
+
assert text == ""
+
assert frgs == []
+
+
+
def test_no_formatting(parser: MarkdownParser):
+
text, frgs = parser.parse("text no formatting!")
+
assert text == "text no formatting!"
+
assert frgs == []
+
+
+
def test_link(parser: MarkdownParser):
+
text, frgs = parser.parse("https://google.com")
+
assert text == "https://google.com"
+
assert len(frgs) == 1
+
+
frg = frgs[0]
+
assert isinstance(frg, f.LinkFragment)
+
assert frg.start == 0 and frg.end == 18
+
assert frg.url == "https://google.com"
+
+
+
def test_link_emojis(parser: MarkdownParser):
+
input = f"{EMOJI} https://google.com"
+
text, frgs = parser.parse(input)
+
assert text == input
+
assert len(frgs) == 1
+
+
frg = frgs[0]
+
assert isinstance(frg, f.LinkFragment)
+
assert frg.start == 9 and frg.end == 27
+
assert frg.url == "https://google.com"
+
+
+
def test_label_link(parser: MarkdownParser):
+
text, frgs = parser.parse("[hello](https://google.com)")
+
assert text == "hello"
+
assert len(frgs) == 1
+
+
frg = frgs[0]
+
assert isinstance(frg, f.LinkFragment)
+
assert frg.start == 0 and frg.end == 5
+
assert frg.url == "https://google.com"
+
+
+
def test_label_link_emojis(parser: MarkdownParser):
+
input = f"[{EMOJI}]( https://google.com)"
+
text, frgs = parser.parse(input)
+
assert text == EMOJI
+
assert len(frgs) == 1
+
+
frg = frgs[0]
+
assert isinstance(frg, f.LinkFragment)
+
assert frg.start == 0 and frg.end == 8
+
assert frg.url == "https://google.com"
+
+
+
def test_tag(parser: MarkdownParser):
+
input = "#testing"
+
text, frgs = parser.parse(input)
+
assert text == input
+
assert len(frgs) == 1
+
+
frg = frgs[0]
+
assert isinstance(frg, f.TagFragment)
+
assert frg.start == 0 and frg.end == 8
+
assert frg.tag == "testing"
+
+
def test_tag_emojis(parser: MarkdownParser):
+
input = f"{EMOJI} #testing"
+
text, frgs = parser.parse(input)
+
assert text == input
+
assert len(frgs) == 1
+
+
frg = frgs[0]
+
assert isinstance(frg, f.TagFragment)
+
assert frg.start == 9 and frg.end == 17
+
assert frg.tag == "testing"
+
+
def test_mention(parser: MarkdownParser):
+
input = "@zen@merping.synth.download"
+
text, frgs = parser.parse(input)
+
assert text == input
+
assert len(frgs) == 1
+
+
frg = frgs[0]
+
assert isinstance(frg, f.MentionFragment)
+
assert frg.start == 0 and frg.end == 27
+
assert frg.uri == "zen@merping.synth.download"
+
+
def test_mention_emojis(parser: MarkdownParser):
+
input = f"{EMOJI} @zen@merping.synth.download"
+
text, frgs = parser.parse(input)
+
assert text == input
+
assert len(frgs) == 1
+
+
frg = frgs[0]
+
assert isinstance(frg, f.MentionFragment)
+
assert frg.start == 9 and frg.end == 36
+
assert frg.uri == "zen@merping.synth.download"
+
+
def test_mixed(parser: MarkdownParser):
+
input = "#testing_tag @zen@merping.synth.download [hello](https://zenfyr.dev/) hii! https://example.com"
+
text, frgs = parser.parse(input)
+
+
expected_text = "#testing_tag @zen@merping.synth.download hello hii! https://example.com"
+
assert text == expected_text
+
assert len(frgs) == 4
+
+
assert isinstance(frgs[0], f.TagFragment)
+
assert frgs[0].start == 0 and frgs[0].end == 12
+
assert frgs[0].tag == "testing_tag"
+
+
assert isinstance(frgs[1], f.MentionFragment)
+
assert frgs[1].start == 13 and frgs[1].end == 40
+
assert frgs[1].uri == "zen@merping.synth.download"
+
+
assert isinstance(frgs[2], f.LinkFragment)
+
assert frgs[2].start == 41 and frgs[2].end == 46
+
assert frgs[2].url == "https://zenfyr.dev/"
+
+
assert isinstance(frgs[3], f.LinkFragment)
+
assert frgs[3].start == 52 and frgs[3].end == 71
+
assert frgs[3].url == "https://example.com"
+
+
def test_mixed_html(parser: MarkdownParser):
+
input = f"<p>#testing_tag @zen@merping.synth.download</p> {EMOJI} <a href=\"https://zenfyr.dev/\"><b>hello</b></a> hii! https://example.com"
+
text, frgs = parser.parse(input)
+
+
expected_text = f"#testing_tag @zen@merping.synth.download\n\n {EMOJI} **hello** hii! https://example.com"
+
assert text == expected_text
+
assert len(frgs) == 4
+
+
assert isinstance(frgs[0], f.TagFragment)
+
assert frgs[0].start == 0 and frgs[0].end == 12
+
assert frgs[0].tag == "testing_tag"
+
+
assert isinstance(frgs[1], f.MentionFragment)
+
assert frgs[1].start == 13 and frgs[1].end == 40
+
assert frgs[1].uri == "zen@merping.synth.download"
+
+
assert isinstance(frgs[2], f.LinkFragment)
+
assert frgs[2].start == 52 and frgs[2].end == 61
+
assert frgs[2].url == "https://zenfyr.dev/"
+
+
assert isinstance(frgs[3], f.LinkFragment)
+
assert frgs[3].start == 67 and frgs[3].end == 86
+
assert frgs[3].url == "https://example.com"
+61
tests/util/util_test.py
···
+
import util.util as u
+
from unittest.mock import patch
+
import pytest
+
+
+
def test_normalize_service_url_http():
+
assert u.normalize_service_url("http://example.com") == "http://example.com"
+
assert u.normalize_service_url("http://example.com/") == "http://example.com"
+
+
+
def test_normalize_service_url_invalid_schemes():
+
with pytest.raises(ValueError, match="Invalid service url"):
+
_ = u.normalize_service_url("ftp://example.com")
+
with pytest.raises(ValueError, match="Invalid service url"):
+
_ = u.normalize_service_url("example.com")
+
with pytest.raises(ValueError, match="Invalid service url"):
+
_ = u.normalize_service_url("//example.com")
+
+
+
def test_read_env_missing_env_var():
+
data = {"token": "env:MISSING_VAR", "keep": "value"}
+
with patch.dict("os.environ", {}, clear=True):
+
u.read_env(data)
+
assert data == {"keep": "value"}
+
assert "token" not in data
+
+
+
def test_read_env_no_env_prefix():
+
data = {"token": "literal_value", "number": 123}
+
u.read_env(data)
+
assert data == {"token": "literal_value", "number": 123}
+
+
+
def test_read_env_deeply_nested():
+
data = {"level1": {"level2": {"token": "env:DEEP_TOKEN"}}}
+
with patch.dict("os.environ", {"DEEP_TOKEN": "deep_secret"}):
+
u.read_env(data)
+
assert data["level1"]["level2"]["token"] == "deep_secret"
+
+
+
def test_read_env_mixed_types():
+
data = {
+
"string": "env:TOKEN",
+
"number": 42,
+
"list": [1, 2, 3],
+
"none": None,
+
"bool": True,
+
}
+
with patch.dict("os.environ", {"TOKEN": "secret"}):
+
u.read_env(data)
+
assert data["string"] == "secret"
+
assert data["number"] == 42
+
assert data["list"] == [1, 2, 3]
+
assert data["none"] is None
+
assert data["bool"] is True
+
+
+
def test_read_env_empty_dict():
+
data = {}
+
u.read_env(data)
+
assert data == {}
+69
uv.lock
···
]
[[package]]
+
name = "colorama"
+
version = "0.4.6"
+
source = { registry = "https://pypi.org/simple" }
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+
wheels = [
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+
]
+
+
[[package]]
name = "dnspython"
version = "2.8.0"
source = { registry = "https://pypi.org/simple" }
···
]
[[package]]
+
name = "iniconfig"
+
version = "2.3.0"
+
source = { registry = "https://pypi.org/simple" }
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+
wheels = [
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+
]
+
+
[[package]]
+
name = "packaging"
+
version = "25.0"
+
source = { registry = "https://pypi.org/simple" }
+
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
+
wheels = [
+
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
+
]
+
+
[[package]]
+
name = "pluggy"
+
version = "1.6.0"
+
source = { registry = "https://pypi.org/simple" }
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+
wheels = [
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+
]
+
+
[[package]]
+
name = "pygments"
+
version = "2.19.2"
+
source = { registry = "https://pypi.org/simple" }
+
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+
wheels = [
+
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+
]
+
+
[[package]]
+
name = "pytest"
+
version = "8.4.2"
+
source = { registry = "https://pypi.org/simple" }
+
dependencies = [
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
+
{ name = "iniconfig" },
+
{ name = "packaging" },
+
{ name = "pluggy" },
+
{ name = "pygments" },
+
]
+
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
+
wheels = [
+
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
+
]
+
+
[[package]]
name = "python-magic"
version = "0.4.27"
source = { registry = "https://pypi.org/simple" }
···
{ name = "websockets" },
]
+
[package.dev-dependencies]
+
dev = [
+
{ name = "pytest" },
+
]
+
[package.metadata]
requires-dist = [
{ name = "dnspython", specifier = ">=2.8.0" },
···
{ name = "requests", specifier = ">=2.32.5" },
{ name = "websockets", specifier = ">=15.0.1" },
]
+
+
[package.metadata.requires-dev]
+
dev = [{ name = "pytest", specifier = ">=8.4.2" }]