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

start adding tests

zenfyr.dev eda25493 749c26db

verified
+329
+14
.tangled/workflows/run-tests.yml
··· 1 + when: 2 + - event: ["push", "manual"] 3 + branch: ["next"] 4 + 5 + engine: nixery 6 + 7 + dependencies: 8 + nixpkgs: 9 + - uv 10 + 11 + steps: 12 + - name: run tests 13 + command: | 14 + uv run pytest -vv
+8
pyproject.toml
··· 11 11 "requests>=2.32.5", 12 12 "websockets>=15.0.1", 13 13 ] 14 + 15 + [dependency-groups] 16 + dev = [ 17 + "pytest>=8.4.2", 18 + ] 19 + 20 + [tool.pytest.ini_options] 21 + pythonpath = ["."]
+16
tests/util/html_test.py
··· 1 + import html 2 + from util.html import HTMLToFragmentsParser 3 + import cross.fragments as f 4 + import pytest 5 + 6 + @pytest.fixture() 7 + def parser(): 8 + return HTMLToFragmentsParser() 9 + 10 + def test_html(parser: HTMLToFragmentsParser): 11 + 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>" 12 + parser.feed(input) 13 + text, fragments = parser.get_result() 14 + 15 + # TODO 16 + #assert text == "~~excuse~~ *me*, **test** post\n\n> very testy post\n\n```\ncat << food\n```\n"
+161
tests/util/markdown_test.py
··· 1 + from util.markdown import MarkdownParser 2 + import cross.fragments as f 3 + import pytest 4 + 5 + EMOJI = "🤬🤬" 6 + 7 + 8 + @pytest.fixture() 9 + def parser(): 10 + return MarkdownParser() 11 + 12 + 13 + def test_empty(parser: MarkdownParser): 14 + text, frgs = parser.parse("") 15 + assert text == "" 16 + assert frgs == [] 17 + 18 + 19 + def test_no_formatting(parser: MarkdownParser): 20 + text, frgs = parser.parse("text no formatting!") 21 + assert text == "text no formatting!" 22 + assert frgs == [] 23 + 24 + 25 + def test_link(parser: MarkdownParser): 26 + text, frgs = parser.parse("https://google.com") 27 + assert text == "https://google.com" 28 + assert len(frgs) == 1 29 + 30 + frg = frgs[0] 31 + assert isinstance(frg, f.LinkFragment) 32 + assert frg.start == 0 and frg.end == 18 33 + assert frg.url == "https://google.com" 34 + 35 + 36 + def test_link_emojis(parser: MarkdownParser): 37 + input = f"{EMOJI} https://google.com" 38 + text, frgs = parser.parse(input) 39 + assert text == input 40 + assert len(frgs) == 1 41 + 42 + frg = frgs[0] 43 + assert isinstance(frg, f.LinkFragment) 44 + assert frg.start == 9 and frg.end == 27 45 + assert frg.url == "https://google.com" 46 + 47 + 48 + def test_label_link(parser: MarkdownParser): 49 + text, frgs = parser.parse("[hello](https://google.com)") 50 + assert text == "hello" 51 + assert len(frgs) == 1 52 + 53 + frg = frgs[0] 54 + assert isinstance(frg, f.LinkFragment) 55 + assert frg.start == 0 and frg.end == 5 56 + assert frg.url == "https://google.com" 57 + 58 + 59 + def test_label_link_emojis(parser: MarkdownParser): 60 + input = f"[{EMOJI}]( https://google.com)" 61 + text, frgs = parser.parse(input) 62 + assert text == EMOJI 63 + assert len(frgs) == 1 64 + 65 + frg = frgs[0] 66 + assert isinstance(frg, f.LinkFragment) 67 + assert frg.start == 0 and frg.end == 8 68 + assert frg.url == "https://google.com" 69 + 70 + 71 + def test_tag(parser: MarkdownParser): 72 + input = "#testing" 73 + text, frgs = parser.parse(input) 74 + assert text == input 75 + assert len(frgs) == 1 76 + 77 + frg = frgs[0] 78 + assert isinstance(frg, f.TagFragment) 79 + assert frg.start == 0 and frg.end == 8 80 + assert frg.tag == "testing" 81 + 82 + def test_tag_emojis(parser: MarkdownParser): 83 + input = f"{EMOJI} #testing" 84 + text, frgs = parser.parse(input) 85 + assert text == input 86 + assert len(frgs) == 1 87 + 88 + frg = frgs[0] 89 + assert isinstance(frg, f.TagFragment) 90 + assert frg.start == 9 and frg.end == 17 91 + assert frg.tag == "testing" 92 + 93 + def test_mention(parser: MarkdownParser): 94 + input = "@zen@merping.synth.download" 95 + text, frgs = parser.parse(input) 96 + assert text == input 97 + assert len(frgs) == 1 98 + 99 + frg = frgs[0] 100 + assert isinstance(frg, f.MentionFragment) 101 + assert frg.start == 0 and frg.end == 27 102 + assert frg.uri == "zen@merping.synth.download" 103 + 104 + def test_mention_emojis(parser: MarkdownParser): 105 + input = f"{EMOJI} @zen@merping.synth.download" 106 + text, frgs = parser.parse(input) 107 + assert text == input 108 + assert len(frgs) == 1 109 + 110 + frg = frgs[0] 111 + assert isinstance(frg, f.MentionFragment) 112 + assert frg.start == 9 and frg.end == 36 113 + assert frg.uri == "zen@merping.synth.download" 114 + 115 + def test_mixed(parser: MarkdownParser): 116 + input = "#testing_tag @zen@merping.synth.download [hello](https://zenfyr.dev/) hii! https://example.com" 117 + text, frgs = parser.parse(input) 118 + 119 + expected_text = "#testing_tag @zen@merping.synth.download hello hii! https://example.com" 120 + assert text == expected_text 121 + assert len(frgs) == 4 122 + 123 + assert isinstance(frgs[0], f.TagFragment) 124 + assert frgs[0].start == 0 and frgs[0].end == 12 125 + assert frgs[0].tag == "testing_tag" 126 + 127 + assert isinstance(frgs[1], f.MentionFragment) 128 + assert frgs[1].start == 13 and frgs[1].end == 40 129 + assert frgs[1].uri == "zen@merping.synth.download" 130 + 131 + assert isinstance(frgs[2], f.LinkFragment) 132 + assert frgs[2].start == 41 and frgs[2].end == 46 133 + assert frgs[2].url == "https://zenfyr.dev/" 134 + 135 + assert isinstance(frgs[3], f.LinkFragment) 136 + assert frgs[3].start == 52 and frgs[3].end == 71 137 + assert frgs[3].url == "https://example.com" 138 + 139 + def test_mixed_html(parser: MarkdownParser): 140 + input = f"<p>#testing_tag @zen@merping.synth.download</p> {EMOJI} <a href=\"https://zenfyr.dev/\"><b>hello</b></a> hii! https://example.com" 141 + text, frgs = parser.parse(input) 142 + 143 + expected_text = f"#testing_tag @zen@merping.synth.download\n\n {EMOJI} **hello** hii! https://example.com" 144 + assert text == expected_text 145 + assert len(frgs) == 4 146 + 147 + assert isinstance(frgs[0], f.TagFragment) 148 + assert frgs[0].start == 0 and frgs[0].end == 12 149 + assert frgs[0].tag == "testing_tag" 150 + 151 + assert isinstance(frgs[1], f.MentionFragment) 152 + assert frgs[1].start == 13 and frgs[1].end == 40 153 + assert frgs[1].uri == "zen@merping.synth.download" 154 + 155 + assert isinstance(frgs[2], f.LinkFragment) 156 + assert frgs[2].start == 52 and frgs[2].end == 61 157 + assert frgs[2].url == "https://zenfyr.dev/" 158 + 159 + assert isinstance(frgs[3], f.LinkFragment) 160 + assert frgs[3].start == 67 and frgs[3].end == 86 161 + assert frgs[3].url == "https://example.com"
+61
tests/util/util_test.py
··· 1 + import util.util as u 2 + from unittest.mock import patch 3 + import pytest 4 + 5 + 6 + def test_normalize_service_url_http(): 7 + assert u.normalize_service_url("http://example.com") == "http://example.com" 8 + assert u.normalize_service_url("http://example.com/") == "http://example.com" 9 + 10 + 11 + def test_normalize_service_url_invalid_schemes(): 12 + with pytest.raises(ValueError, match="Invalid service url"): 13 + _ = u.normalize_service_url("ftp://example.com") 14 + with pytest.raises(ValueError, match="Invalid service url"): 15 + _ = u.normalize_service_url("example.com") 16 + with pytest.raises(ValueError, match="Invalid service url"): 17 + _ = u.normalize_service_url("//example.com") 18 + 19 + 20 + def test_read_env_missing_env_var(): 21 + data = {"token": "env:MISSING_VAR", "keep": "value"} 22 + with patch.dict("os.environ", {}, clear=True): 23 + u.read_env(data) 24 + assert data == {"keep": "value"} 25 + assert "token" not in data 26 + 27 + 28 + def test_read_env_no_env_prefix(): 29 + data = {"token": "literal_value", "number": 123} 30 + u.read_env(data) 31 + assert data == {"token": "literal_value", "number": 123} 32 + 33 + 34 + def test_read_env_deeply_nested(): 35 + data = {"level1": {"level2": {"token": "env:DEEP_TOKEN"}}} 36 + with patch.dict("os.environ", {"DEEP_TOKEN": "deep_secret"}): 37 + u.read_env(data) 38 + assert data["level1"]["level2"]["token"] == "deep_secret" 39 + 40 + 41 + def test_read_env_mixed_types(): 42 + data = { 43 + "string": "env:TOKEN", 44 + "number": 42, 45 + "list": [1, 2, 3], 46 + "none": None, 47 + "bool": True, 48 + } 49 + with patch.dict("os.environ", {"TOKEN": "secret"}): 50 + u.read_env(data) 51 + assert data["string"] == "secret" 52 + assert data["number"] == 42 53 + assert data["list"] == [1, 2, 3] 54 + assert data["none"] is None 55 + assert data["bool"] is True 56 + 57 + 58 + def test_read_env_empty_dict(): 59 + data = {} 60 + u.read_env(data) 61 + assert data == {}
+69
uv.lock
··· 69 69 ] 70 70 71 71 [[package]] 72 + name = "colorama" 73 + version = "0.4.6" 74 + source = { registry = "https://pypi.org/simple" } 75 + 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" } 76 + wheels = [ 77 + { 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" }, 78 + ] 79 + 80 + [[package]] 72 81 name = "dnspython" 73 82 version = "2.8.0" 74 83 source = { registry = "https://pypi.org/simple" } ··· 93 102 ] 94 103 95 104 [[package]] 105 + name = "iniconfig" 106 + version = "2.3.0" 107 + source = { registry = "https://pypi.org/simple" } 108 + 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" } 109 + wheels = [ 110 + { 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" }, 111 + ] 112 + 113 + [[package]] 114 + name = "packaging" 115 + version = "25.0" 116 + source = { registry = "https://pypi.org/simple" } 117 + 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" } 118 + wheels = [ 119 + { 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" }, 120 + ] 121 + 122 + [[package]] 123 + name = "pluggy" 124 + version = "1.6.0" 125 + source = { registry = "https://pypi.org/simple" } 126 + 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" } 127 + wheels = [ 128 + { 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" }, 129 + ] 130 + 131 + [[package]] 132 + name = "pygments" 133 + version = "2.19.2" 134 + source = { registry = "https://pypi.org/simple" } 135 + 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" } 136 + wheels = [ 137 + { 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" }, 138 + ] 139 + 140 + [[package]] 141 + name = "pytest" 142 + version = "8.4.2" 143 + source = { registry = "https://pypi.org/simple" } 144 + dependencies = [ 145 + { name = "colorama", marker = "sys_platform == 'win32'" }, 146 + { name = "iniconfig" }, 147 + { name = "packaging" }, 148 + { name = "pluggy" }, 149 + { name = "pygments" }, 150 + ] 151 + 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" } 152 + wheels = [ 153 + { 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" }, 154 + ] 155 + 156 + [[package]] 96 157 name = "python-magic" 97 158 version = "0.4.27" 98 159 source = { registry = "https://pypi.org/simple" } ··· 168 229 { name = "websockets" }, 169 230 ] 170 231 232 + [package.dev-dependencies] 233 + dev = [ 234 + { name = "pytest" }, 235 + ] 236 + 171 237 [package.metadata] 172 238 requires-dist = [ 173 239 { name = "dnspython", specifier = ">=2.8.0" }, ··· 176 242 { name = "requests", specifier = ">=2.32.5" }, 177 243 { name = "websockets", specifier = ">=15.0.1" }, 178 244 ] 245 + 246 + [package.metadata.requires-dev] 247 + dev = [{ name = "pytest", specifier = ">=8.4.2" }]