Skip to main content

tests/python/test_urls.py

1"""Tests for `chives.urls`."""
3from pathlib import Path
5import pytest
6from vcr.cassette import Cassette
8from chives.urls import (
9 clean_youtube_url,
10 is_mastodon_host,
11 is_url_safe,
12 parse_mastodon_post_url,
13 parse_tumblr_post_url,
17@pytest.mark.parametrize(
18 "url, cleaned_url",
19 [
20 (
21 "https://www.youtube.com/watch?v=2OHPPSew2nY&list=WL&index=6&t=193s",
22 "https://www.youtube.com/watch?v=2OHPPSew2nY",
23 ),
24 (
25 "https://www.youtube.com/watch?v=2OHPPSew2nY",
26 "https://www.youtube.com/watch?v=2OHPPSew2nY",
27 ),
28 (
29 "https://www.youtube.com/watch?v=WiIi7STG3e0&start_radio=1",
30 "https://www.youtube.com/watch?v=WiIi7STG3e0",
31 ),
32 (
33 "https://www.youtube.com/watch?app=desktop&v=vQ4ATsyD4hA",
34 "https://www.youtube.com/watch?v=vQ4ATsyD4hA",
35 ),
36 ],
38def test_clean_youtube_url(url: str, cleaned_url: str) -> None:
39 """
40 All the query parameters get stripped from YouTube URLs correctly.
41 """
42 assert clean_youtube_url(url) == cleaned_url
45@pytest.mark.parametrize(
46 "url, server, acct, post_id",
47 [
48 pytest.param(
49 "https://iconfactory.world/@Iconfactory/115650922400392083",
50 "iconfactory.world",
51 "Iconfactory",
52 "115650922400392083",
53 id="iconfactory",
54 ),
55 pytest.param(
56 "https://social.alexwlchan.net/@chris__martin@functional.cafe/113369395383537892",
57 "functional.cafe",
58 "chris__martin",
59 "113369395366414375",
60 id="alexwlchan_redirect",
61 ),
62 pytest.param(
63 "https://social.alexwlchan.net/@alex/116300317590482708",
64 "social.alexwlchan.net",
65 "alex",
66 "116300317590482708",
67 id="alexwlchan_self",
68 ),
69 ],
71def test_parse_mastodon_post_url(
72 vcr_cassette: Cassette, url: str, server: str, acct: str, post_id: str
73) -> None:
74 """
75 Mastodon post URLs are parsed correctly.
76 """
77 assert parse_mastodon_post_url(url) == (server, acct, post_id)
80@pytest.mark.parametrize(
81 "url, error",
82 [
83 pytest.param(
84 "https://mastodon.social/", "Cannot parse Mastodon URL", id="no_path"
85 ),
86 pytest.param(
87 "https://mastodon.social/about",
88 "Cannot parse Mastodon URL",
89 id="no_post_id",
90 ),
91 pytest.param(
92 "https://mastodon.social/about/subdir", "Cannot find `acct`", id="no_acct"
93 ),
94 pytest.param(
95 "https://mastodon.social/@example/about",
96 "Mastodon post ID is not numeric",
97 id="non_numeric_post_id",
98 ),
99 pytest.param(
100 "https://social.alexwlchan.net/@does@not.exist/123",
101 "Cannot parse Mastodon URL",
102 id="alexwlchan_does_not_exist",
103 ),
104 ],
106def test_parse_mastodon_post_url_errors(
107 vcr_cassette: Cassette, url: str, error: str
108) -> None:
109 """
110 parse_mastodon_post_url returns a useful error if it can't parse the URL.
111 """
112 with pytest.raises(ValueError, match=error):
113 parse_mastodon_post_url(url)
116@pytest.mark.parametrize(
117 "url, blog_identifier, post_id",
118 [
119 (
120 "https://www.tumblr.com/kynvillingur/792473255236796416/",
121 "kynvillingur",
122 "792473255236796416",
123 ),
124 (
125 "https://cut3panda.tumblr.com/post/94093772689/for-some-people-the-more-you-get-to-know-them",
126 "cut3panda",
127 "94093772689",
128 ),
129 ],
131def test_parse_tumblr_post_url(url: str, blog_identifier: str, post_id: str) -> None:
132 """
133 Tumblr URLs are parsed correctly.
134 """
135 assert parse_tumblr_post_url(url) == (blog_identifier, post_id)
138@pytest.mark.parametrize(
139 "url",
140 [
141 "https://www.tumblr.com/",
142 "https://www.tumblr.com/staff/",
143 "https://staff.tumblr.com/",
144 "https://www.example.com/",
145 ],
147def test_parse_bad_tumblr_url(url: str) -> None:
148 """
149 Parsing a non-Tumblr URL throws a ValueError.
150 """
151 with pytest.raises(ValueError, match="Cannot parse Tumblr URL"):
152 parse_tumblr_post_url(url)
155class TestIsMastodonHost:
156 """
157 Tests for `is_mastodon_host`.
158 """
160 @pytest.mark.parametrize(
161 "host", ["mastodon.social", "hachyderm.io", "social.jvns.ca"]
162 )
163 def test_mastodon_servers(self, host: str, vcr_cassette: Cassette) -> None:
164 """
165 It correctly identifies real Mastodon servers.
166 """
167 assert is_mastodon_host(host)
169 @pytest.mark.parametrize(
170 "host",
171 [
172 # These are regular Internet websites which don't expose
173 # the /.well-known/nodeinfo endpoint
174 "example.com",
175 "tailscale.com",
176 #
177 # PeerTube exposes /.well-known/nodeinfo, but it's running
178 # different software.
179 "peertube.tv",
180 #
181 # A website with a known bad SSL certificate, which is assumed
182 # not to be a Mastodon host because we can't connect to it.
183 "expired.badssl.com",
184 ],
185 )
186 def test_non_mastodon_servers(self, host: str, vcr_cassette: Cassette) -> None:
187 """
188 Other websites are not Mastodon servers.
189 """
190 assert not is_mastodon_host(host)
193class TestIsUrlSafe:
194 """
195 Tests for `is_url_safe`.
196 """
198 @pytest.mark.parametrize("path", ["example.txt", Path("a/b/cat.jpg")])
199 def test_safe(self, path: str | Path) -> None:
200 """Paths which are URL safe."""
201 assert is_url_safe(path)
203 @pytest.mark.parametrize("path", ["is it?", Path("cat%c.jpg"), "a#b"])
204 def test_unsafe(self, path: str | Path) -> None:
205 """Paths which are not URL safe."""
206 assert not is_url_safe(path)