Add domain-wide blacklist and whitelist functionality

This commit is contained in:
Joakim Hellsén 2026-04-12 23:51:05 +02:00
commit bdbd46ebd4
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
14 changed files with 930 additions and 305 deletions

View file

@ -221,6 +221,264 @@ def test_get() -> None:
assert response.status_code == 200, f"/whitelist failed: {response.text}"
def test_post_blacklist_apply_to_domain_updates_global_domain_blacklist() -> None:
"""Posting blacklist with apply_to_domain should save domain-wide blacklist values."""
reader: Reader = get_reader_dependency()
# Ensure webhook exists and feed can be created.
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
client.post(url="/remove", data={"feed_url": feed_url})
response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
response = client.post(
url="/blacklist",
data={
"feed_url": feed_url,
"blacklist_author": "TheLovinator",
"apply_to_domain": "true",
},
)
assert response.status_code == 200, f"Failed to post blacklist: {response.text}"
domain_blacklist = reader.get_tag((), "domain_blacklist", {})
assert isinstance(domain_blacklist, dict), "domain_blacklist should be a dict"
assert "lovinator.space" in domain_blacklist, "Expected domain key in domain_blacklist"
assert domain_blacklist["lovinator.space"]["blacklist_author"] == "TheLovinator"
def test_post_whitelist_apply_to_domain_updates_global_domain_whitelist() -> None:
"""Posting whitelist with apply_to_domain should save domain-wide whitelist values."""
reader: Reader = get_reader_dependency()
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
client.post(url="/remove", data={"feed_url": feed_url})
response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
response = client.post(
url="/whitelist",
data={
"feed_url": feed_url,
"whitelist_author": "TheLovinator",
"apply_to_domain": "true",
},
)
assert response.status_code == 200, f"Failed to post whitelist: {response.text}"
domain_whitelist = reader.get_tag((), "domain_whitelist", {})
assert isinstance(domain_whitelist, dict), "domain_whitelist should be a dict"
assert "lovinator.space" in domain_whitelist, "Expected domain key in domain_whitelist"
assert domain_whitelist["lovinator.space"]["whitelist_author"] == "TheLovinator"
def test_domain_filter_pages_show_domain_enabled_notice() -> None:
"""Blacklist and whitelist pages should show domain-wide enabled notices when configured."""
reader: Reader = get_reader_dependency()
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
client.post(url="/remove", data={"feed_url": feed_url})
response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
reader.set_tag(
(),
"domain_blacklist",
{"lovinator.space": {"blacklist_title": "spoiler"}},
) # pyright: ignore[reportArgumentType]
reader.set_tag(
(),
"domain_whitelist",
{"lovinator.space": {"whitelist_title": "release"}},
) # pyright: ignore[reportArgumentType]
response = client.get(url="/blacklist", params={"feed_url": encoded_feed_url(feed_url)})
assert response.status_code == 200, f"/blacklist failed: {response.text}"
assert "Domain-wide blacklist is enabled for lovinator.space." in response.text
response = client.get(url="/whitelist", params={"feed_url": encoded_feed_url(feed_url)})
assert response.status_code == 200, f"/whitelist failed: {response.text}"
assert "Domain-wide whitelist is enabled for lovinator.space." in response.text
def test_domain_blacklist_isolation_between_domains() -> None:
"""Applying domain blacklist should not overwrite other domains."""
reader: Reader = get_reader_dependency()
reader.set_tag((), "domain_blacklist", {"example.com": {"blacklist_title": "existing"}}) # pyright: ignore[reportArgumentType]
response = client.post(
url="/blacklist",
data={
"feed_url": feed_url,
"blacklist_author": "TheLovinator",
"apply_to_domain": "true",
},
)
assert response.status_code == 200, f"Failed to post blacklist: {response.text}"
domain_blacklist = reader.get_tag((), "domain_blacklist", {})
assert isinstance(domain_blacklist, dict)
assert domain_blacklist["example.com"]["blacklist_title"] == "existing"
assert domain_blacklist["lovinator.space"]["blacklist_author"] == "TheLovinator"
def test_domain_whitelist_isolation_between_domains() -> None:
"""Applying domain whitelist should not overwrite other domains."""
reader: Reader = get_reader_dependency()
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
client.post(url="/remove", data={"feed_url": feed_url})
response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
reader.set_tag((), "domain_whitelist", {"example.com": {"whitelist_title": "existing"}}) # pyright: ignore[reportArgumentType]
response = client.post(
url="/whitelist",
data={
"feed_url": feed_url,
"whitelist_author": "TheLovinator",
"apply_to_domain": "true",
},
)
assert response.status_code == 200, f"Failed to post whitelist: {response.text}"
domain_whitelist = reader.get_tag((), "domain_whitelist", {})
assert isinstance(domain_whitelist, dict)
assert domain_whitelist["example.com"]["whitelist_title"] == "existing"
assert domain_whitelist["lovinator.space"]["whitelist_author"] == "TheLovinator"
def test_domain_blacklist_removed_when_apply_to_domain_and_empty_values() -> None:
"""Submitting empty domain blacklist values should remove existing domain entry."""
reader: Reader = get_reader_dependency()
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
client.post(url="/remove", data={"feed_url": feed_url})
response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
reader.set_tag((), "domain_blacklist", {"lovinator.space": {"blacklist_title": "existing"}}) # pyright: ignore[reportArgumentType]
response = client.post(
url="/blacklist",
data={"feed_url": feed_url, "apply_to_domain": "true"},
)
assert response.status_code == 200, f"Failed to post blacklist: {response.text}"
domain_blacklist = reader.get_tag((), "domain_blacklist", {})
assert isinstance(domain_blacklist, dict)
assert "lovinator.space" not in domain_blacklist
def test_domain_whitelist_removed_when_apply_to_domain_and_empty_values() -> None:
"""Submitting empty domain whitelist values should remove existing domain entry."""
reader: Reader = get_reader_dependency()
reader.set_tag((), "domain_whitelist", {"lovinator.space": {"whitelist_title": "existing"}}) # pyright: ignore[reportArgumentType]
response = client.post(
url="/whitelist",
data={"feed_url": feed_url, "apply_to_domain": "true"},
)
assert response.status_code == 200, f"Failed to post whitelist: {response.text}"
domain_whitelist = reader.get_tag((), "domain_whitelist", {})
assert isinstance(domain_whitelist, dict)
assert "lovinator.space" not in domain_whitelist
def test_apply_to_domain_missing_does_not_update_domain_tags() -> None:
"""When apply_to_domain is omitted, domain tags should not change."""
reader: Reader = get_reader_dependency()
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
client.post(url="/remove", data={"feed_url": feed_url})
response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
reader.set_tag((), "domain_blacklist", {}) # pyright: ignore[reportArgumentType]
reader.set_tag((), "domain_whitelist", {}) # pyright: ignore[reportArgumentType]
response = client.post(
url="/blacklist",
data={"feed_url": feed_url, "blacklist_author": "TheLovinator"},
)
assert response.status_code == 200, f"Failed to post blacklist: {response.text}"
response = client.post(
url="/whitelist",
data={"feed_url": feed_url, "whitelist_author": "TheLovinator"},
)
assert response.status_code == 200, f"Failed to post whitelist: {response.text}"
assert reader.get_tag((), "domain_blacklist", {}) == {}
assert reader.get_tag((), "domain_whitelist", {}) == {}
def test_apply_to_domain_invalid_value_rejected() -> None:
"""Invalid boolean value for apply_to_domain should return validation error."""
response = client.post(
url="/blacklist",
data={
"feed_url": feed_url,
"blacklist_author": "TheLovinator",
"apply_to_domain": "invalid-bool",
},
)
assert response.status_code == 422, f"Expected 422 for invalid boolean: {response.text}"
def test_index_shows_domain_filter_shortcuts() -> None:
"""Index should show domain whitelist/blacklist shortcut buttons."""
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
client.post(url="/remove", data={"feed_url": feed_url})
response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
response = client.get(url="/")
assert response.status_code == 200, f"Failed to get /: {response.text}"
assert "Domain whitelist" in response.text
assert "Domain blacklist" in response.text
assert f"/whitelist?feed_url={encoded_feed_url(feed_url)}" in response.text
assert f"/blacklist?feed_url={encoded_feed_url(feed_url)}" in response.text
def test_settings_page_shows_screenshot_layout_setting() -> None:
response: Response = client.get(url="/settings")
assert response.status_code == 200, f"/settings failed: {response.text}"