lastgenre: Test blacklist feature

- test blacklist patterns
- test file format
- test complex regex patterns
- test invalid regex gets escaped
This commit is contained in:
J0J0 Todos 2025-08-05 07:33:58 +02:00
parent fb65d852c5
commit 5d333dca3b

View file

@ -14,12 +14,17 @@
"""Tests for the 'lastgenre' plugin.""" """Tests for the 'lastgenre' plugin."""
import os
import re
import tempfile
from collections import defaultdict
from unittest.mock import Mock from unittest.mock import Mock
import pytest import pytest
from beets.test import _common from beets.test import _common
from beets.test.helper import BeetsTestCase from beets.test.helper import BeetsTestCase
from beets.ui import UserError
from beetsplug import lastgenre from beetsplug import lastgenre
@ -544,3 +549,159 @@ def test_get_genre(config_values, item_genre, mock_genres, expected_result):
# Run # Run
res = plugin._get_genre(item) res = plugin._get_genre(item)
assert res == expected_result assert res == expected_result
@pytest.mark.parametrize(
"blacklist_dict, artist, genre, expected_forbidden",
[
# Global blacklist - simple word
({"*": ["spoken word"]}, "Any Artist", "spoken word", True),
({"*": ["spoken word"]}, "Any Artist", "jazz", False),
# Global blacklist - regex pattern
({"*": [".*electronic.*"]}, "Any Artist", "ambient electronic", True),
({"*": [".*electronic.*"]}, "Any Artist", "jazz", False),
# Artist-specific blacklist
({"metallica": ["metal"]}, "Metallica", "metal", True),
({"metallica": ["metal"]}, "Iron Maiden", "metal", False),
# Case insensitive matching
({"metallica": ["metal"]}, "METALLICA", "METAL", True),
# Artist-specific blacklist - exact match
({"metallica": ["^Heavy Metal$"]}, "Metallica", "classic metal", False),
# Combined global and artist-specific
(
{"*": ["spoken word"], "metallica": ["metal"]},
"Metallica",
"spoken word",
True,
),
(
{"*": ["spoken word"], "metallica": ["metal"]},
"Metallica",
"metal",
True,
),
# Complex regex pattern with multiple features (raw string)
(
{
"fracture": [
r"^(heavy|black|power|death)?\s?(metal|rock)$|\w+-metal\d*$"
]
},
"Fracture",
"power metal",
True,
),
# Complex regex pattern with multiple features (regular string)
(
{"amon tobin": ["d(rum)?[ n/]*b(ass)?"]},
"Amon Tobin",
"dnb",
True,
),
# Empty blacklist
({}, "Any Artist", "any genre", False),
],
)
def test_blacklist_patterns(blacklist_dict, artist, genre, expected_forbidden):
"""Test blacklist pattern matching logic directly."""
# Initialize plugin
plugin = lastgenre.LastGenrePlugin()
# Set up compiled blacklist directly (skipping file parsing)
compiled_blacklist = defaultdict(list)
for artist_name, patterns in blacklist_dict.items():
compiled_blacklist[artist_name.lower()] = [
re.compile(pattern) for pattern in patterns
]
plugin.blacklist = compiled_blacklist
# Test the _is_forbidden method
result = plugin._is_forbidden(genre, artist)
assert result == expected_forbidden
@pytest.mark.parametrize(
"file_content, expected_blacklist",
[
# Basic artist with pattern
("metallica:\n metal", {"metallica": ["metal"]}),
# Global blacklist
("*:\n spoken word", {"*": ["spoken word"]}),
# Multiple patterns per artist
(
"metallica:\n metal\n .*rock.*",
{"metallica": ["metal", ".*rock.*"]},
),
# Comments and empty lines ignored
(
"# comment\n*:\n spoken word\n\nmetallica:\n metal",
{"*": ["spoken word"], "metallica": ["metal"]},
),
# Case insensitive artist names
("METALLICA:\n METAL", {"metallica": ["metal"]}),
# Invalid regex pattern that gets escaped
("artist:\n [invalid(regex", {"artist": ["\\[invalid\\(regex"]}),
# Empty file
("", {}),
],
)
def test_blacklist_file_format(file_content, expected_blacklist):
"""Test blacklist file format parsing."""
with tempfile.NamedTemporaryFile(
mode="w", suffix=".txt", delete=False, encoding="utf-8"
) as f:
f.write(file_content)
blacklist_file = f.name
try:
plugin = lastgenre.LastGenrePlugin()
plugin.config["blacklist"] = blacklist_file
blacklist_result = plugin._load_blacklist()
# Convert compiled regex patterns back to strings for comparison
string_blacklist = {}
for artist, compiled_patterns in blacklist_result.items():
string_blacklist[artist] = [
pattern.pattern for pattern in compiled_patterns
]
assert string_blacklist == expected_blacklist
finally:
os.unlink(blacklist_file)
@pytest.mark.parametrize(
"invalid_content, expected_error_message",
[
# Missing colon
("metallica\n metal", "Malformed blacklist section header"),
# Pattern before section
(" metal\nmetallica:\n heavy metal", "before any section header"),
# Unindented pattern
("metallica:\nmetal", "Malformed blacklist section header"),
],
)
def test_blacklist_file_format_errors(invalid_content, expected_error_message):
"""Test blacklist file format error handling."""
with tempfile.NamedTemporaryFile(
mode="w", suffix=".txt", delete=False, encoding="utf-8"
) as f:
f.write(invalid_content)
blacklist_file = f.name
try:
plugin = lastgenre.LastGenrePlugin()
plugin.config["blacklist"] = blacklist_file
with pytest.raises(UserError) as exc_info:
plugin._load_blacklist()
assert expected_error_message in str(exc_info.value)
finally:
os.unlink(blacklist_file)