/
/
/
1"""Tests for the filesystem provider helpers."""
2
3from pathlib import Path
4
5from music_assistant.providers.filesystem_local.helpers import (
6 FileSystemItem,
7 get_folder_signature,
8 sorted_scandir,
9)
10
11
12def _file_item(name: str, checksum: str = "1700000000", file_size: int = 1024) -> FileSystemItem:
13 """
14 Build a FileSystemItem for a file, without touching the filesystem.
15
16 :param name: Relative path of the file.
17 :param checksum: Last modified time of the file.
18 :param file_size: Size of the file in bytes.
19 """
20 return FileSystemItem(
21 filename=name.rsplit("/", 1)[-1],
22 relative_path=name,
23 absolute_path=f"/media/{name}",
24 is_dir=False,
25 checksum=checksum,
26 file_size=file_size,
27 )
28
29
30def test_sorted_scandir_natural_order(tmp_path: Path) -> None:
31 """Entries are returned in natural, case insensitive order when sort is enabled."""
32 (tmp_path / "Incoming").mkdir()
33 (tmp_path / "albums").mkdir()
34 for name in ("10 - Third.flac", "2 - Second.flac", "1 - First.flac", "cover.jpg"):
35 (tmp_path / name).touch()
36
37 result = sorted_scandir(str(tmp_path), str(tmp_path), sort=True)
38
39 assert [item.filename for item in result] == [
40 "1 - First.flac",
41 "2 - Second.flac",
42 "10 - Third.flac",
43 "albums",
44 "cover.jpg",
45 "Incoming",
46 ]
47
48
49def test_sorted_scandir_unsorted_by_default(tmp_path: Path) -> None:
50 """Without the sort flag, entries are returned in raw scandir order."""
51 for name in ("b.flac", "a.flac"):
52 (tmp_path / name).touch()
53
54 result = sorted_scandir(str(tmp_path), str(tmp_path))
55
56 assert sorted(item.filename for item in result) == ["a.flac", "b.flac"]
57
58
59def test_folder_signature_ignores_item_order() -> None:
60 """The same set of files always produces the same signature."""
61 items = [_file_item("pod/ep1.mp3"), _file_item("pod/ep2.mp3")]
62
63 assert get_folder_signature(items) == get_folder_signature(list(reversed(items)))
64
65
66def test_folder_signature_detects_changes() -> None:
67 """Adding, removing, replacing or retagging a file changes the signature."""
68 items = [_file_item("pod/ep1.mp3"), _file_item("pod/ep2.mp3")]
69 signature = get_folder_signature(items)
70
71 assert get_folder_signature([*items, _file_item("pod/ep3.mp3")]) != signature
72 assert get_folder_signature(items[:1]) != signature
73 # same number of files, but one of them replaced, retagged or renamed
74 for changed in (
75 _file_item("pod/ep2.mp3", checksum="1800000000"),
76 _file_item("pod/ep2.mp3", file_size=2048),
77 _file_item("pod/renamed.mp3"),
78 ):
79 assert get_folder_signature([items[0], changed]) != signature
80
81
82def test_folder_signature_cannot_be_forged_by_a_filename() -> None:
83 """A filename spelling out another entry does not collide with the entries it names."""
84 real = [_file_item("a.mp3"), _file_item("b.mp3", checksum="1800000000", file_size=2048)]
85 forged = [_file_item("a.mp3:1700000000:1024|b.mp3", checksum="1800000000", file_size=2048)]
86
87 assert get_folder_signature(forged) != get_folder_signature(real)
88