/
/
/
1"""Tests for the Bandcamp composite-artist-ID utilities."""
2
3from __future__ import annotations
4
5import pytest
6
7from music_assistant.providers.bandcamp._ids import (
8 make_artist_id,
9 parse_artist_id,
10 slugify_performer,
11)
12
13
14class TestSlugifyPerformer:
15 """Slug generation rules for performer names."""
16
17 def test_lowercases_alphanumerics(self) -> None:
18 """Bare alphabetic names round-trip lowercased with no separators."""
19 assert slugify_performer("Mortaja") == "mortaja"
20
21 def test_collapses_spaces_to_single_hyphen(self) -> None:
22 """Spaces between words collapse to one hyphen each."""
23 assert slugify_performer("The Green Arrows") == "the-green-arrows"
24
25 def test_collapses_repeated_punctuation(self) -> None:
26 """Runs of any non-alnum chars collapse into a single separator."""
27 assert slugify_performer("Apollo Brown / OC") == "apollo-brown-oc"
28
29 def test_normalizes_ampersand_to_and(self) -> None:
30 """Ampersands use the same identity spelling as the word ``and``."""
31 assert slugify_performer("Apollo Brown & OC") == "apollo-brown-and-oc"
32 assert slugify_performer("Apollo Brown and OC") == "apollo-brown-and-oc"
33
34 def test_strips_leading_and_trailing_separators(self) -> None:
35 """Leading/trailing separators are removed."""
36 assert slugify_performer(" --foo-- ") == "foo"
37
38 def test_unicode_letters_are_dropped(self) -> None:
39 """Non-ASCII letters are intentionally dropped (lossy slug)."""
40 # Slug is intentionally lossy: non-ASCII letters do not round-trip.
41 # This is acceptable because slugs are scoped per band_id, so the
42 # only failure mode is rare same-band performer-name collisions.
43 assert slugify_performer("Adiós Mundo Cruel") == "adi-s-mundo-cruel"
44
45 def test_empty_input_returns_empty(self) -> None:
46 """Empty input yields empty output, not a crash."""
47 assert slugify_performer("") == ""
48
49 def test_only_punctuation_returns_empty(self) -> None:
50 """Strings with no slug-eligible characters yield empty output."""
51 assert slugify_performer("///") == ""
52
53
54class TestMakeArtistId:
55 """Composite ID construction from band_id + performer."""
56
57 def test_no_performer_returns_plain_band_id(self) -> None:
58 """Omitted performer means "the band itself" â plain numeric ID."""
59 assert make_artist_id(123) == "123"
60
61 def test_none_performer_returns_plain_band_id(self) -> None:
62 """Explicit None performer is treated the same as omitted."""
63 assert make_artist_id(123, None) == "123"
64
65 def test_empty_performer_returns_plain_band_id(self) -> None:
66 """Empty string performer is treated the same as omitted."""
67 assert make_artist_id(123, "") == "123"
68
69 def test_with_performer_synthesizes(self) -> None:
70 """Provided performer name produces a synthetic `{band_id}:{slug}` form."""
71 assert make_artist_id(441379041, "Mortaja") == "441379041:mortaja"
72
73 def test_string_band_id_is_accepted(self) -> None:
74 """band_id may be passed as a string for caller convenience."""
75 assert make_artist_id("441379041", "Mortaja") == "441379041:mortaja"
76
77 def test_performer_with_only_punctuation_falls_back_to_plain(self) -> None:
78 """A performer whose slug is empty degrades to the plain band ID."""
79 # If the slug ends up empty we cannot build a useful synthetic
80 # ID, so we degrade to the band's plain ID rather than emit a
81 # malformed `123:` value.
82 assert make_artist_id(123, "///") == "123"
83
84
85class TestParseArtistId:
86 """Reverse of make_artist_id."""
87
88 def test_plain_band_id(self) -> None:
89 """A plain numeric ID parses with no performer slug."""
90 assert parse_artist_id("123") == (123, None)
91
92 def test_synthetic_id(self) -> None:
93 """A synthetic ID splits into (band_id, slug)."""
94 assert parse_artist_id("441379041:mortaja") == (441379041, "mortaja")
95
96 def test_synthetic_with_hyphenated_slug(self) -> None:
97 """Hyphens inside the slug are preserved (not a separator with band_id)."""
98 assert parse_artist_id("123:the-green-arrows") == (123, "the-green-arrows")
99
100 def test_non_numeric_band_id_raises(self) -> None:
101 """Non-numeric band_id portions surface as ValueError."""
102 with pytest.raises(ValueError): # noqa: PT011
103 parse_artist_id("abc:slug")
104