Got browse caching working
This commit is contained in:
@@ -3,7 +3,7 @@ from time import sleep
|
||||
|
||||
import pytest
|
||||
|
||||
from sublime.adapters import AdapterManager, Result
|
||||
from sublime.adapters import AdapterManager, Result, SearchResult
|
||||
from sublime.config import AppConfiguration, ServerConfiguration
|
||||
|
||||
|
||||
@@ -114,3 +114,22 @@ def test_get_song_details(adapter_manager: AdapterManager):
|
||||
# assert 0
|
||||
# TODO
|
||||
pass
|
||||
|
||||
|
||||
def test_search(adapter_manager: AdapterManager):
|
||||
# TODO
|
||||
return
|
||||
results = []
|
||||
|
||||
# TODO ingest data
|
||||
|
||||
def search_callback(result: SearchResult):
|
||||
results.append((result.artists, result.albums, result.songs, result.playlists))
|
||||
|
||||
AdapterManager.search("ohea", search_callback=search_callback).result()
|
||||
|
||||
# TODO test getting results from the server and updating using that
|
||||
while len(results) < 1:
|
||||
sleep(0.1)
|
||||
|
||||
assert len(results) == 1
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import shutil
|
||||
from dataclasses import asdict
|
||||
from datetime import timedelta
|
||||
@@ -27,8 +28,8 @@ MOCK_SONG_FILE2_HASH = "c32597c724e2e484dbf5856930b2e5bb80de13b7"
|
||||
MOCK_SUBSONIC_SONGS = [
|
||||
SubsonicAPI.Song(
|
||||
"2",
|
||||
"Song 2",
|
||||
_parent="foo",
|
||||
title="Song 2",
|
||||
parent_id="d1",
|
||||
_album="foo",
|
||||
album_id="a1",
|
||||
_artist="cool",
|
||||
@@ -40,8 +41,8 @@ MOCK_SUBSONIC_SONGS = [
|
||||
),
|
||||
SubsonicAPI.Song(
|
||||
"1",
|
||||
"Song 1",
|
||||
_parent="foo",
|
||||
title="Song 1",
|
||||
parent_id="d1",
|
||||
_album="foo",
|
||||
album_id="a1",
|
||||
_artist="foo",
|
||||
@@ -53,8 +54,8 @@ MOCK_SUBSONIC_SONGS = [
|
||||
),
|
||||
SubsonicAPI.Song(
|
||||
"1",
|
||||
"Song 1",
|
||||
_parent="foo",
|
||||
title="Song 1",
|
||||
parent_id="d1",
|
||||
_album="foo",
|
||||
album_id="a1",
|
||||
_artist="foo",
|
||||
@@ -66,6 +67,8 @@ MOCK_SUBSONIC_SONGS = [
|
||||
),
|
||||
]
|
||||
|
||||
KEYS = FilesystemAdapter.CachedDataKey
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def adapter(tmp_path: Path):
|
||||
@@ -100,7 +103,7 @@ def verify_songs(
|
||||
assert len(actual_songs) == len(expected_songs)
|
||||
for actual, song in zip(actual_songs, expected_songs):
|
||||
for k, v in asdict(song).items():
|
||||
if k in ("_genre", "_album", "_artist", "_parent", "album_id", "artist_id"):
|
||||
if k in ("_genre", "_album", "_artist", "album_id", "artist_id"):
|
||||
continue
|
||||
print(k, "->", v) # noqa: T001
|
||||
|
||||
@@ -110,8 +113,6 @@ def verify_songs(
|
||||
assert ("a1", "foo") == (actual_value.id, actual_value.name)
|
||||
elif k == "genre":
|
||||
assert v["name"] == actual_value.name
|
||||
elif k == "parent":
|
||||
assert "foo" == actual_value.id
|
||||
elif k == "artist":
|
||||
assert (v["id"], v["name"]) == (actual_value.id, actual_value.name)
|
||||
else:
|
||||
@@ -123,7 +124,7 @@ def test_caching_get_playlists(cache_adapter: FilesystemAdapter):
|
||||
cache_adapter.get_playlists()
|
||||
|
||||
# Ingest an empty list (for example, no playlists added yet to server).
|
||||
cache_adapter.ingest_new_data(FilesystemAdapter.CachedDataKey.PLAYLISTS, (), [])
|
||||
cache_adapter.ingest_new_data(KEYS.PLAYLISTS, (), [])
|
||||
|
||||
# After the first cache miss of get_playlists, even if an empty list is
|
||||
# returned, the next one should not be a cache miss.
|
||||
@@ -131,7 +132,7 @@ def test_caching_get_playlists(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# Ingest two playlists.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLISTS,
|
||||
KEYS.PLAYLISTS,
|
||||
(),
|
||||
[
|
||||
SubsonicAPI.Playlist("1", "test1", comment="comment"),
|
||||
@@ -150,7 +151,7 @@ def test_caching_get_playlists(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# Ingest a new playlist list with one of them deleted.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLISTS,
|
||||
KEYS.PLAYLISTS,
|
||||
(),
|
||||
[
|
||||
SubsonicAPI.Playlist("1", "test1", comment="comment"),
|
||||
@@ -186,7 +187,7 @@ def test_caching_get_playlist_details(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# Simulate the playlist being retrieved from Subsonic.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLIST_DETAILS,
|
||||
KEYS.PLAYLIST_DETAILS,
|
||||
("1",),
|
||||
SubsonicAPI.PlaylistWithSongs("1", "test1", songs=MOCK_SUBSONIC_SONGS[:2]),
|
||||
)
|
||||
@@ -200,7 +201,7 @@ def test_caching_get_playlist_details(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# "Force refresh" the playlist and add a new song (duplicate).
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLIST_DETAILS,
|
||||
KEYS.PLAYLIST_DETAILS,
|
||||
("1",),
|
||||
SubsonicAPI.PlaylistWithSongs("1", "foo", songs=MOCK_SUBSONIC_SONGS),
|
||||
)
|
||||
@@ -231,7 +232,7 @@ def test_no_caching_get_playlist_details(adapter: FilesystemAdapter):
|
||||
def test_caching_get_playlist_then_details(cache_adapter: FilesystemAdapter):
|
||||
# Ingest a list of playlists (like the sidebar, without songs)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLISTS,
|
||||
KEYS.PLAYLISTS,
|
||||
(),
|
||||
[SubsonicAPI.Playlist("1", "test1"), SubsonicAPI.Playlist("2", "test2")],
|
||||
)
|
||||
@@ -248,13 +249,11 @@ def test_caching_get_playlist_then_details(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# Simulate getting playlist details for id=1, then id=2
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLIST_DETAILS,
|
||||
("1",),
|
||||
SubsonicAPI.PlaylistWithSongs("1", "test1"),
|
||||
KEYS.PLAYLIST_DETAILS, ("1",), SubsonicAPI.PlaylistWithSongs("1", "test1"),
|
||||
)
|
||||
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLIST_DETAILS,
|
||||
KEYS.PLAYLIST_DETAILS,
|
||||
("2",),
|
||||
SubsonicAPI.PlaylistWithSongs("2", "test2", songs=MOCK_SUBSONIC_SONGS),
|
||||
)
|
||||
@@ -272,7 +271,7 @@ def test_cache_cover_art(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# After ingesting the data, reading from the cache should give the exact same file.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.COVER_ART_FILE, ("pl_test1",), MOCK_ALBUM_ART,
|
||||
KEYS.COVER_ART_FILE, ("pl_test1",), MOCK_ALBUM_ART,
|
||||
)
|
||||
with open(cache_adapter.get_cover_art_uri("pl_test1", "file"), "wb+") as cached:
|
||||
with open(MOCK_ALBUM_ART, "wb+") as expected:
|
||||
@@ -281,32 +280,28 @@ def test_cache_cover_art(cache_adapter: FilesystemAdapter):
|
||||
|
||||
def test_invalidate_playlist(cache_adapter: FilesystemAdapter):
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLISTS,
|
||||
KEYS.PLAYLISTS,
|
||||
(),
|
||||
[SubsonicAPI.Playlist("1", "test1"), SubsonicAPI.Playlist("2", "test2")],
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.COVER_ART_FILE, ("pl_test1",), MOCK_ALBUM_ART,
|
||||
KEYS.COVER_ART_FILE, ("pl_test1",), MOCK_ALBUM_ART,
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLIST_DETAILS,
|
||||
KEYS.PLAYLIST_DETAILS,
|
||||
("2",),
|
||||
SubsonicAPI.PlaylistWithSongs("2", "test2", cover_art="pl_2", songs=[]),
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.COVER_ART_FILE, ("pl_2",), MOCK_ALBUM_ART2,
|
||||
KEYS.COVER_ART_FILE, ("pl_2",), MOCK_ALBUM_ART2,
|
||||
)
|
||||
|
||||
stale_uri_1 = cache_adapter.get_cover_art_uri("pl_test1", "file")
|
||||
stale_uri_2 = cache_adapter.get_cover_art_uri("pl_2", "file")
|
||||
|
||||
cache_adapter.invalidate_data(FilesystemAdapter.CachedDataKey.PLAYLISTS, ())
|
||||
cache_adapter.invalidate_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLIST_DETAILS, ("2",)
|
||||
)
|
||||
cache_adapter.invalidate_data(
|
||||
FilesystemAdapter.CachedDataKey.COVER_ART_FILE, ("pl_test1",)
|
||||
)
|
||||
cache_adapter.invalidate_data(KEYS.PLAYLISTS, ())
|
||||
cache_adapter.invalidate_data(KEYS.PLAYLIST_DETAILS, ("2",))
|
||||
cache_adapter.invalidate_data(KEYS.COVER_ART_FILE, ("pl_test1",))
|
||||
|
||||
# After invalidating the data, it should cache miss, but still have the old, stale,
|
||||
# data.
|
||||
@@ -341,21 +336,16 @@ def test_invalidate_playlist(cache_adapter: FilesystemAdapter):
|
||||
|
||||
|
||||
def test_invalidate_song_file(cache_adapter: FilesystemAdapter):
|
||||
CACHE_KEYS = FilesystemAdapter.CachedDataKey
|
||||
cache_adapter.ingest_new_data(KEYS.SONG, ("2",), MOCK_SUBSONIC_SONGS[0])
|
||||
cache_adapter.ingest_new_data(KEYS.SONG, ("1",), MOCK_SUBSONIC_SONGS[1])
|
||||
cache_adapter.ingest_new_data(
|
||||
CACHE_KEYS.SONG_DETAILS, ("2",), MOCK_SUBSONIC_SONGS[0]
|
||||
KEYS.COVER_ART_FILE, ("s1", "song"), MOCK_ALBUM_ART,
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
CACHE_KEYS.SONG_DETAILS, ("1",), MOCK_SUBSONIC_SONGS[1]
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
CACHE_KEYS.COVER_ART_FILE, ("s1", "song"), MOCK_ALBUM_ART,
|
||||
)
|
||||
cache_adapter.ingest_new_data(CACHE_KEYS.SONG_FILE, ("1",), MOCK_SONG_FILE)
|
||||
cache_adapter.ingest_new_data(CACHE_KEYS.SONG_FILE, ("2",), MOCK_SONG_FILE2)
|
||||
cache_adapter.ingest_new_data(KEYS.SONG_FILE, ("1",), MOCK_SONG_FILE)
|
||||
cache_adapter.ingest_new_data(KEYS.SONG_FILE, ("2",), MOCK_SONG_FILE2)
|
||||
|
||||
cache_adapter.invalidate_data(CACHE_KEYS.SONG_FILE, ("1",))
|
||||
cache_adapter.invalidate_data(CACHE_KEYS.COVER_ART_FILE, ("s1", "song"))
|
||||
cache_adapter.invalidate_data(KEYS.SONG_FILE, ("1",))
|
||||
cache_adapter.invalidate_data(KEYS.COVER_ART_FILE, ("s1", "song"))
|
||||
|
||||
with pytest.raises(CacheMissError):
|
||||
cache_adapter.get_song_uri("1", "file")
|
||||
@@ -369,21 +359,21 @@ def test_invalidate_song_file(cache_adapter: FilesystemAdapter):
|
||||
|
||||
def test_delete_playlists(cache_adapter: FilesystemAdapter):
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLIST_DETAILS,
|
||||
KEYS.PLAYLIST_DETAILS,
|
||||
("1",),
|
||||
SubsonicAPI.PlaylistWithSongs("1", "test1", cover_art="pl_1", songs=[]),
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.PLAYLIST_DETAILS,
|
||||
KEYS.PLAYLIST_DETAILS,
|
||||
("2",),
|
||||
SubsonicAPI.PlaylistWithSongs("2", "test1", cover_art="pl_2", songs=[]),
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.COVER_ART_FILE, ("pl_1",), MOCK_ALBUM_ART,
|
||||
KEYS.COVER_ART_FILE, ("pl_1",), MOCK_ALBUM_ART,
|
||||
)
|
||||
|
||||
# Deleting a playlist should get rid of it entirely.
|
||||
cache_adapter.delete_data(FilesystemAdapter.CachedDataKey.PLAYLIST_DETAILS, ("2",))
|
||||
cache_adapter.delete_data(KEYS.PLAYLIST_DETAILS, ("2",))
|
||||
try:
|
||||
cache_adapter.get_playlist_details("2")
|
||||
assert 0, "DID NOT raise CacheMissError"
|
||||
@@ -391,7 +381,7 @@ def test_delete_playlists(cache_adapter: FilesystemAdapter):
|
||||
assert e.partial_data is None
|
||||
|
||||
# Deleting a playlist with associated cover art should get rid the cover art too.
|
||||
cache_adapter.delete_data(FilesystemAdapter.CachedDataKey.PLAYLIST_DETAILS, ("1",))
|
||||
cache_adapter.delete_data(KEYS.PLAYLIST_DETAILS, ("1",))
|
||||
try:
|
||||
cache_adapter.get_cover_art_uri("pl_1", "file")
|
||||
assert 0, "DID NOT raise CacheMissError"
|
||||
@@ -410,21 +400,17 @@ def test_delete_playlists(cache_adapter: FilesystemAdapter):
|
||||
|
||||
|
||||
def test_delete_song_data(cache_adapter: FilesystemAdapter):
|
||||
cache_adapter.ingest_new_data(KEYS.SONG, ("1",), MOCK_SUBSONIC_SONGS[1])
|
||||
cache_adapter.ingest_new_data(KEYS.SONG_FILE, ("1",), MOCK_SONG_FILE)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.SONG_DETAILS, ("1",), MOCK_SUBSONIC_SONGS[1]
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.SONG_FILE, ("1",), MOCK_SONG_FILE
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.COVER_ART_FILE, ("s1",), MOCK_ALBUM_ART,
|
||||
KEYS.COVER_ART_FILE, ("s1",), MOCK_ALBUM_ART,
|
||||
)
|
||||
|
||||
music_file_path = cache_adapter.get_song_uri("1", "file")
|
||||
cover_art_path = cache_adapter.get_cover_art_uri("s1", "file")
|
||||
|
||||
cache_adapter.delete_data(FilesystemAdapter.CachedDataKey.SONG_FILE, ("1",))
|
||||
cache_adapter.delete_data(FilesystemAdapter.CachedDataKey.COVER_ART_FILE, ("s1",))
|
||||
cache_adapter.delete_data(KEYS.SONG_FILE, ("1",))
|
||||
cache_adapter.delete_data(KEYS.COVER_ART_FILE, ("s1",))
|
||||
|
||||
assert not Path(music_file_path).exists()
|
||||
assert not Path(cover_art_path).exists()
|
||||
@@ -446,12 +432,8 @@ def test_caching_get_genres(cache_adapter: FilesystemAdapter):
|
||||
with pytest.raises(CacheMissError):
|
||||
cache_adapter.get_genres()
|
||||
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.SONG_DETAILS, ("2",), MOCK_SUBSONIC_SONGS[0]
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.SONG_DETAILS, ("1",), MOCK_SUBSONIC_SONGS[1]
|
||||
)
|
||||
cache_adapter.ingest_new_data(KEYS.SONG, ("2",), MOCK_SUBSONIC_SONGS[0])
|
||||
cache_adapter.ingest_new_data(KEYS.SONG, ("1",), MOCK_SUBSONIC_SONGS[1])
|
||||
|
||||
# Getting genres now should look at what's on the songs. This sould cache miss, but
|
||||
# still give some data.
|
||||
@@ -463,7 +445,7 @@ def test_caching_get_genres(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# After we actually ingest the actual list, it should be returned instead.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.GENRES,
|
||||
KEYS.GENRES,
|
||||
(),
|
||||
[
|
||||
SubsonicAPI.Genre("Bar", 10, 20),
|
||||
@@ -479,9 +461,7 @@ def test_caching_get_song_details(cache_adapter: FilesystemAdapter):
|
||||
cache_adapter.get_song_details("1")
|
||||
|
||||
# Simulate the song details being retrieved from Subsonic.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.SONG_DETAILS, ("1",), MOCK_SUBSONIC_SONGS[1]
|
||||
)
|
||||
cache_adapter.ingest_new_data(KEYS.SONG, ("1",), MOCK_SUBSONIC_SONGS[1])
|
||||
|
||||
song = cache_adapter.get_song_details("1")
|
||||
assert song.id == "1"
|
||||
@@ -489,19 +469,19 @@ def test_caching_get_song_details(cache_adapter: FilesystemAdapter):
|
||||
assert song.album
|
||||
assert (song.album.id, song.album.name) == ("a1", "foo")
|
||||
assert song.artist and song.artist.name == "foo"
|
||||
assert song.parent.id == "foo"
|
||||
assert song.parent_id == "d1"
|
||||
assert song.duration == timedelta(seconds=10.2)
|
||||
assert song.path == "foo/song1.mp3"
|
||||
assert song.genre and song.genre.name == "Foo"
|
||||
|
||||
# "Force refresh" the song details
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.SONG_DETAILS,
|
||||
KEYS.SONG,
|
||||
("1",),
|
||||
SubsonicAPI.Song(
|
||||
"1",
|
||||
"Song 1",
|
||||
_parent="bar",
|
||||
title="Song 1",
|
||||
parent_id="bar",
|
||||
_album="bar",
|
||||
album_id="a2",
|
||||
_artist="bar",
|
||||
@@ -518,7 +498,7 @@ def test_caching_get_song_details(cache_adapter: FilesystemAdapter):
|
||||
assert song.album
|
||||
assert (song.album.id, song.album.name) == ("a2", "bar")
|
||||
assert song.artist and song.artist.name == "bar"
|
||||
assert song.parent.id == "bar"
|
||||
assert song.parent_id == "bar"
|
||||
assert song.duration == timedelta(seconds=10.2)
|
||||
assert song.path == "bar/song1.mp3"
|
||||
assert song.genre and song.genre.name == "Bar"
|
||||
@@ -529,12 +509,12 @@ def test_caching_get_song_details(cache_adapter: FilesystemAdapter):
|
||||
|
||||
def test_caching_less_info(cache_adapter: FilesystemAdapter):
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.SONG_DETAILS,
|
||||
KEYS.SONG,
|
||||
("1",),
|
||||
SubsonicAPI.Song(
|
||||
"1",
|
||||
"Song 1",
|
||||
_parent="bar",
|
||||
title="Song 1",
|
||||
parent_id="bar",
|
||||
_album="bar",
|
||||
album_id="a2",
|
||||
_artist="bar",
|
||||
@@ -545,12 +525,12 @@ def test_caching_less_info(cache_adapter: FilesystemAdapter):
|
||||
),
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.SONG_DETAILS,
|
||||
KEYS.SONG,
|
||||
("1",),
|
||||
SubsonicAPI.Song(
|
||||
"1",
|
||||
"Song 1",
|
||||
_parent="bar",
|
||||
title="Song 1",
|
||||
parent_id="bar",
|
||||
duration=timedelta(seconds=10.2),
|
||||
path="bar/song1.mp3",
|
||||
),
|
||||
@@ -568,7 +548,7 @@ def test_caching_get_artists(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# Ingest artists.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.ARTISTS,
|
||||
KEYS.ARTISTS,
|
||||
(),
|
||||
[
|
||||
SubsonicAPI.ArtistAndArtistInfo("1", "test1", album_count=3, albums=[]),
|
||||
@@ -583,7 +563,7 @@ def test_caching_get_artists(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# Ingest a new artists list with one of them deleted.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.ARTISTS,
|
||||
KEYS.ARTISTS,
|
||||
(),
|
||||
[
|
||||
SubsonicAPI.ArtistAndArtistInfo("1", "test1", album_count=3),
|
||||
@@ -603,16 +583,12 @@ def test_caching_get_ignored_articles(cache_adapter: FilesystemAdapter):
|
||||
cache_adapter.get_ignored_articles()
|
||||
|
||||
# Ingest ignored_articles.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.IGNORED_ARTICLES, (), {"Foo", "Bar"}
|
||||
)
|
||||
cache_adapter.ingest_new_data(KEYS.IGNORED_ARTICLES, (), {"Foo", "Bar"})
|
||||
artists = cache_adapter.get_ignored_articles()
|
||||
assert {"Foo", "Bar"} == artists
|
||||
|
||||
# Ingest a new artists list with one of them deleted.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.IGNORED_ARTICLES, (), {"Foo", "Baz"}
|
||||
)
|
||||
cache_adapter.ingest_new_data(KEYS.IGNORED_ARTICLES, (), {"Foo", "Baz"})
|
||||
artists = cache_adapter.get_ignored_articles()
|
||||
assert {"Foo", "Baz"} == artists
|
||||
|
||||
@@ -623,7 +599,7 @@ def test_caching_get_artist(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# Simulate the artist details being retrieved from Subsonic.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.ARTIST,
|
||||
KEYS.ARTIST,
|
||||
("1",),
|
||||
SubsonicAPI.ArtistAndArtistInfo(
|
||||
"1",
|
||||
@@ -658,7 +634,7 @@ def test_caching_get_artist(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# Simulate "force refreshing" the artist details being retrieved from Subsonic.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.ARTIST,
|
||||
KEYS.ARTIST,
|
||||
("1",),
|
||||
SubsonicAPI.ArtistAndArtistInfo(
|
||||
"1",
|
||||
@@ -704,7 +680,7 @@ def test_caching_get_album(cache_adapter: FilesystemAdapter):
|
||||
|
||||
# Simulate the artist details being retrieved from Subsonic.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.ALBUM,
|
||||
KEYS.ALBUM,
|
||||
("a1",),
|
||||
SubsonicAPI.Album(
|
||||
"a1",
|
||||
@@ -739,7 +715,7 @@ def test_caching_get_album(cache_adapter: FilesystemAdapter):
|
||||
def test_caching_invalidate_artist(cache_adapter: FilesystemAdapter):
|
||||
# Simulate the artist details being retrieved from Subsonic.
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.ARTIST,
|
||||
KEYS.ARTIST,
|
||||
("artist1",),
|
||||
SubsonicAPI.ArtistAndArtistInfo(
|
||||
"artist1",
|
||||
@@ -759,23 +735,23 @@ def test_caching_invalidate_artist(cache_adapter: FilesystemAdapter):
|
||||
),
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.ALBUM,
|
||||
KEYS.ALBUM,
|
||||
("1",),
|
||||
SubsonicAPI.Album("1", "Foo", artist_id="artist1", cover_art="1"),
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.ALBUM,
|
||||
KEYS.ALBUM,
|
||||
("2",),
|
||||
SubsonicAPI.Album("2", "Bar", artist_id="artist1", cover_art="2"),
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.COVER_ART_FILE, ("image",), MOCK_ALBUM_ART3,
|
||||
KEYS.COVER_ART_FILE, ("image",), MOCK_ALBUM_ART3,
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.COVER_ART_FILE, ("1",), MOCK_ALBUM_ART,
|
||||
KEYS.COVER_ART_FILE, ("1",), MOCK_ALBUM_ART,
|
||||
)
|
||||
cache_adapter.ingest_new_data(
|
||||
FilesystemAdapter.CachedDataKey.COVER_ART_FILE, ("2",), MOCK_ALBUM_ART2,
|
||||
KEYS.COVER_ART_FILE, ("2",), MOCK_ALBUM_ART2,
|
||||
)
|
||||
|
||||
stale_artist = cache_adapter.get_artist("artist1")
|
||||
@@ -785,7 +761,7 @@ def test_caching_invalidate_artist(cache_adapter: FilesystemAdapter):
|
||||
stale_cover_art_1 = cache_adapter.get_cover_art_uri("1", "file")
|
||||
stale_cover_art_2 = cache_adapter.get_cover_art_uri("2", "file")
|
||||
|
||||
cache_adapter.invalidate_data(FilesystemAdapter.CachedDataKey.ARTIST, ("artist1",))
|
||||
cache_adapter.invalidate_data(KEYS.ARTIST, ("artist1",))
|
||||
|
||||
# Test the cascade of cache invalidations.
|
||||
try:
|
||||
@@ -829,3 +805,45 @@ def test_caching_invalidate_artist(cache_adapter: FilesystemAdapter):
|
||||
except CacheMissError as e:
|
||||
assert e.partial_data
|
||||
assert e.partial_data == stale_cover_art_2
|
||||
|
||||
|
||||
def test_get_music_directory(cache_adapter: FilesystemAdapter):
|
||||
dir_id = "d1"
|
||||
with pytest.raises(CacheMissError):
|
||||
cache_adapter.get_directory(dir_id)
|
||||
|
||||
# Simulate the directory details being retrieved from Subsonic.
|
||||
cache_adapter.ingest_new_data(
|
||||
KEYS.DIRECTORY,
|
||||
(dir_id,),
|
||||
SubsonicAPI.Directory(
|
||||
dir_id,
|
||||
title="foo",
|
||||
parent_id=None,
|
||||
_children=[json.loads(s.to_json()) for s in MOCK_SUBSONIC_SONGS[:2]]
|
||||
+ [
|
||||
{
|
||||
"id": "542",
|
||||
"parent": dir_id,
|
||||
"isDir": True,
|
||||
"title": "Crash My Party",
|
||||
}
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
directory = cache_adapter.get_directory(dir_id)
|
||||
assert directory and directory.id == dir_id
|
||||
assert directory.name == "foo"
|
||||
assert directory.parent_id == "root"
|
||||
|
||||
dir_child, *song_children = directory.children
|
||||
verify_songs(song_children, MOCK_SUBSONIC_SONGS[:2])
|
||||
assert dir_child.id == "542"
|
||||
assert dir_child.parent_id
|
||||
assert dir_child.name == "Crash My Party"
|
||||
|
||||
|
||||
def test_search(adapter: FilesystemAdapter):
|
||||
# TODO
|
||||
pass
|
||||
|
55
tests/adapter_tests/mock_data/get_indexes-airsonic.json
Normal file
55
tests/adapter_tests/mock_data/get_indexes-airsonic.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"subsonic-response": {
|
||||
"status": "ok",
|
||||
"version": "1.15.0",
|
||||
"indexes": {
|
||||
"lastModified": 1588577415000,
|
||||
"ignoredArticles": "The El La Los Las Le Les",
|
||||
"index": [
|
||||
{
|
||||
"name": "A",
|
||||
"artist": [
|
||||
{
|
||||
"id": "73",
|
||||
"name": "The Afters"
|
||||
},
|
||||
{
|
||||
"id": "100",
|
||||
"name": "Adele"
|
||||
},
|
||||
{
|
||||
"id": "120",
|
||||
"name": "Austin French"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "B",
|
||||
"artist": [
|
||||
{
|
||||
"id": "93",
|
||||
"name": "The Band Perry"
|
||||
},
|
||||
{
|
||||
"id": "41",
|
||||
"name": "Basshunter"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "X-Z",
|
||||
"artist": [
|
||||
{
|
||||
"id": "155",
|
||||
"name": "Zac Brown Band"
|
||||
},
|
||||
{
|
||||
"id": "25",
|
||||
"name": "Zach Williams"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"subsonic-response" : {
|
||||
"status" : "ok",
|
||||
"version" : "1.15.0",
|
||||
"directory" : {
|
||||
"id" : "60",
|
||||
"name" : "Luke Bryan",
|
||||
"playCount" : 0,
|
||||
"child" : [ {
|
||||
"id" : "542",
|
||||
"parent" : "60",
|
||||
"isDir" : true,
|
||||
"title" : "Crash My Party",
|
||||
"album" : "Crash My Party",
|
||||
"artist" : "Luke Bryan",
|
||||
"year" : 2013,
|
||||
"genre" : "Country",
|
||||
"coverArt" : "542",
|
||||
"playCount" : 48,
|
||||
"created" : "2020-03-27T05:27:57.000Z"
|
||||
} ]
|
||||
}
|
||||
}
|
||||
}
|
235
tests/adapter_tests/mock_data/search3-airsonic.json
Normal file
235
tests/adapter_tests/mock_data/search3-airsonic.json
Normal file
@@ -0,0 +1,235 @@
|
||||
{
|
||||
"subsonic-response" : {
|
||||
"status" : "ok",
|
||||
"version" : "1.15.0",
|
||||
"searchResult3" : {
|
||||
"artist" : [ {
|
||||
"id" : "25",
|
||||
"name" : "Zach Williams",
|
||||
"coverArt" : "ar-25",
|
||||
"albumCount" : 1
|
||||
}, {
|
||||
"id" : "154",
|
||||
"name" : "Zac Brown Band",
|
||||
"coverArt" : "ar-154",
|
||||
"albumCount" : 3
|
||||
} ],
|
||||
"album" : [ {
|
||||
"id" : "31",
|
||||
"name" : "Chain Breaker",
|
||||
"artist" : "Zach Williams",
|
||||
"artistId" : "25",
|
||||
"coverArt" : "al-31",
|
||||
"songCount" : 1,
|
||||
"duration" : 196,
|
||||
"created" : "2020-03-27T05:32:31.000Z",
|
||||
"year" : 2016,
|
||||
"genre" : "Christian & Gospel"
|
||||
}, {
|
||||
"id" : "235",
|
||||
"name" : "The Foundation",
|
||||
"artist" : "Zac Brown Band",
|
||||
"artistId" : "154",
|
||||
"coverArt" : "al-235",
|
||||
"songCount" : 3,
|
||||
"duration" : 675,
|
||||
"created" : "2020-03-27T05:32:15.000Z",
|
||||
"year" : 2008,
|
||||
"genre" : "Country"
|
||||
}, {
|
||||
"id" : "236",
|
||||
"name" : "Uncaged",
|
||||
"artist" : "Zac Brown Band",
|
||||
"artistId" : "154",
|
||||
"coverArt" : "al-236",
|
||||
"songCount" : 2,
|
||||
"duration" : 602,
|
||||
"created" : "2020-03-27T05:32:24.000Z",
|
||||
"year" : 2012,
|
||||
"genre" : "Country"
|
||||
}, {
|
||||
"id" : "237",
|
||||
"name" : "You Get What You Give",
|
||||
"artist" : "Zac Brown Band",
|
||||
"artistId" : "154",
|
||||
"coverArt" : "al-237",
|
||||
"songCount" : 1,
|
||||
"duration" : 273,
|
||||
"created" : "2020-03-27T05:32:28.000Z",
|
||||
"year" : 2010,
|
||||
"genre" : "Country"
|
||||
} ],
|
||||
"song" : [ {
|
||||
"id" : "246",
|
||||
"parent" : "360",
|
||||
"isDir" : false,
|
||||
"title" : "Chain Breaker",
|
||||
"album" : "Chain Breaker",
|
||||
"artist" : "Zach Williams",
|
||||
"track" : 1,
|
||||
"year" : 2016,
|
||||
"genre" : "Christian & Gospel",
|
||||
"coverArt" : "360",
|
||||
"size" : 7038712,
|
||||
"contentType" : "audio/mp4",
|
||||
"suffix" : "m4a",
|
||||
"transcodedContentType" : "audio/mpeg",
|
||||
"transcodedSuffix" : "mp3",
|
||||
"duration" : 196,
|
||||
"bitRate" : 256,
|
||||
"path" : "Zach Williams/Chain Breaker/01 Chain Breaker.m4a",
|
||||
"isVideo" : false,
|
||||
"playCount" : 8,
|
||||
"discNumber" : 1,
|
||||
"created" : "2020-03-27T05:32:31.000Z",
|
||||
"albumId" : "31",
|
||||
"artistId" : "25",
|
||||
"type" : "music"
|
||||
}, {
|
||||
"id" : "737",
|
||||
"parent" : "738",
|
||||
"isDir" : false,
|
||||
"title" : "Highway 20 Ride",
|
||||
"album" : "The Foundation",
|
||||
"artist" : "Zac Brown Band",
|
||||
"track" : 9,
|
||||
"year" : 2008,
|
||||
"genre" : "Country",
|
||||
"coverArt" : "738",
|
||||
"size" : 7843278,
|
||||
"contentType" : "audio/mpeg",
|
||||
"suffix" : "mp3",
|
||||
"duration" : 229,
|
||||
"bitRate" : 263,
|
||||
"path" : "Zac Brown Band/The Foundation/09 - Highway 20 Ride.mp3",
|
||||
"isVideo" : false,
|
||||
"playCount" : 8,
|
||||
"discNumber" : 1,
|
||||
"created" : "2020-03-27T05:32:15.000Z",
|
||||
"albumId" : "235",
|
||||
"artistId" : "154",
|
||||
"type" : "music"
|
||||
}, {
|
||||
"id" : "743",
|
||||
"parent" : "738",
|
||||
"isDir" : false,
|
||||
"title" : "Chicken Fried",
|
||||
"album" : "The Foundation",
|
||||
"artist" : "Zac Brown Band",
|
||||
"track" : 6,
|
||||
"year" : 2008,
|
||||
"genre" : "Country",
|
||||
"coverArt" : "738",
|
||||
"size" : 8420335,
|
||||
"contentType" : "audio/mpeg",
|
||||
"suffix" : "mp3",
|
||||
"duration" : 238,
|
||||
"bitRate" : 272,
|
||||
"path" : "Zac Brown Band/The Foundation/06 - Chicken Fried.mp3",
|
||||
"isVideo" : false,
|
||||
"playCount" : 9,
|
||||
"discNumber" : 1,
|
||||
"created" : "2020-03-27T05:32:11.000Z",
|
||||
"albumId" : "235",
|
||||
"artistId" : "154",
|
||||
"type" : "music"
|
||||
}, {
|
||||
"id" : "744",
|
||||
"parent" : "738",
|
||||
"isDir" : false,
|
||||
"title" : "Whatever It Is",
|
||||
"album" : "The Foundation",
|
||||
"artist" : "Zac Brown Band",
|
||||
"track" : 2,
|
||||
"year" : 2008,
|
||||
"genre" : "Country",
|
||||
"coverArt" : "738",
|
||||
"size" : 7313167,
|
||||
"contentType" : "audio/mpeg",
|
||||
"suffix" : "mp3",
|
||||
"duration" : 208,
|
||||
"bitRate" : 269,
|
||||
"path" : "Zac Brown Band/The Foundation/02 - Whatever It Is.mp3",
|
||||
"isVideo" : false,
|
||||
"playCount" : 17,
|
||||
"discNumber" : 1,
|
||||
"created" : "2020-03-27T05:32:08.000Z",
|
||||
"albumId" : "235",
|
||||
"artistId" : "154",
|
||||
"type" : "music"
|
||||
}, {
|
||||
"id" : "739",
|
||||
"parent" : "740",
|
||||
"isDir" : false,
|
||||
"title" : "Sweet Annie",
|
||||
"album" : "Uncaged",
|
||||
"artist" : "Zac Brown Band",
|
||||
"track" : 6,
|
||||
"year" : 2012,
|
||||
"genre" : "Country",
|
||||
"coverArt" : "740",
|
||||
"size" : 9523591,
|
||||
"contentType" : "audio/mpeg",
|
||||
"suffix" : "mp3",
|
||||
"duration" : 278,
|
||||
"bitRate" : 265,
|
||||
"path" : "Zac Brown Band/Uncaged/06 - Sweet Annie.mp3",
|
||||
"isVideo" : false,
|
||||
"playCount" : 10,
|
||||
"discNumber" : 1,
|
||||
"created" : "2020-03-27T05:32:24.000Z",
|
||||
"albumId" : "236",
|
||||
"artistId" : "154",
|
||||
"type" : "music"
|
||||
}, {
|
||||
"id" : "745",
|
||||
"parent" : "740",
|
||||
"isDir" : false,
|
||||
"title" : "Goodbye In Her Eyes",
|
||||
"album" : "Uncaged",
|
||||
"artist" : "Zac Brown Band",
|
||||
"track" : 3,
|
||||
"year" : 2012,
|
||||
"genre" : "Country",
|
||||
"coverArt" : "740",
|
||||
"size" : 11111186,
|
||||
"contentType" : "audio/mpeg",
|
||||
"suffix" : "mp3",
|
||||
"duration" : 324,
|
||||
"bitRate" : 267,
|
||||
"path" : "Zac Brown Band/Uncaged/03 - Goodbye In Her Eyes.mp3",
|
||||
"isVideo" : false,
|
||||
"playCount" : 7,
|
||||
"discNumber" : 1,
|
||||
"created" : "2020-03-27T05:32:20.000Z",
|
||||
"albumId" : "236",
|
||||
"artistId" : "154",
|
||||
"type" : "music"
|
||||
}, {
|
||||
"id" : "741",
|
||||
"parent" : "742",
|
||||
"isDir" : false,
|
||||
"title" : "Colder Weather",
|
||||
"album" : "You Get What You Give",
|
||||
"artist" : "Zac Brown Band",
|
||||
"track" : 8,
|
||||
"year" : 2010,
|
||||
"genre" : "Country",
|
||||
"coverArt" : "742",
|
||||
"size" : 9088683,
|
||||
"contentType" : "audio/mpeg",
|
||||
"suffix" : "mp3",
|
||||
"duration" : 273,
|
||||
"bitRate" : 255,
|
||||
"path" : "Zac Brown Band/You Get What You Give/08 - Colder Weather.mp3",
|
||||
"isVideo" : false,
|
||||
"playCount" : 8,
|
||||
"discNumber" : 1,
|
||||
"created" : "2020-03-27T05:32:28.000Z",
|
||||
"albumId" : "237",
|
||||
"artistId" : "154",
|
||||
"type" : "music"
|
||||
} ]
|
||||
}
|
||||
}
|
||||
}
|
@@ -58,6 +58,7 @@ def mock_data_files(
|
||||
num_files += 1
|
||||
yield file, iter(parts)
|
||||
|
||||
# Make sure that is at least one test file
|
||||
assert num_files > 0
|
||||
|
||||
|
||||
@@ -158,7 +159,7 @@ def test_get_playlist_details(adapter: SubsonicAdapter):
|
||||
# Make sure that at least the first song got decoded properly.
|
||||
assert playlist_details.songs[0] == SubsonicAPI.Song(
|
||||
id="202",
|
||||
_parent="318",
|
||||
parent_id="318",
|
||||
title="What a Beautiful Name",
|
||||
_album="What a Beautiful Name - Single",
|
||||
album_id="48",
|
||||
@@ -201,7 +202,7 @@ def test_create_playlist(adapter: SubsonicAdapter):
|
||||
songs=[
|
||||
SubsonicAPI.Song(
|
||||
id="202",
|
||||
_parent="318",
|
||||
parent_id="318",
|
||||
title="What a Beautiful Name",
|
||||
_album="What a Beautiful Name - Single",
|
||||
album_id="48",
|
||||
@@ -263,8 +264,8 @@ def test_get_song_details(adapter: SubsonicAdapter):
|
||||
"544",
|
||||
timedelta(seconds=203),
|
||||
)
|
||||
assert song.path.endswith("Sweet Caroline.mp3")
|
||||
assert song.parent and song.parent.id == "544"
|
||||
assert song.path and song.path.endswith("Sweet Caroline.mp3")
|
||||
assert song.parent_id == "544"
|
||||
assert song.artist
|
||||
assert (song.artist.id, song.artist.name) == ("60", "Neil Diamond")
|
||||
assert song.album
|
||||
@@ -425,3 +426,48 @@ def test_get_album(adapter: SubsonicAdapter):
|
||||
"Nothing Like You",
|
||||
"Better Together",
|
||||
]
|
||||
|
||||
|
||||
def test_get_music_directory(adapter: SubsonicAdapter):
|
||||
for filename, data in mock_data_files("get_music_directory"):
|
||||
logging.info(filename)
|
||||
logging.debug(data)
|
||||
adapter._set_mock_data(data)
|
||||
|
||||
directory = adapter.get_directory("3")
|
||||
assert directory.id == "60"
|
||||
assert directory.name == "Luke Bryan"
|
||||
assert directory.parent_id == "root"
|
||||
assert directory.children and len(directory.children) == 1
|
||||
child = directory.children[0]
|
||||
assert isinstance(child, SubsonicAPI.Directory)
|
||||
assert child.id == "542"
|
||||
assert child.name == "Crash My Party"
|
||||
assert child.parent_id == "60"
|
||||
|
||||
for filename, data in mock_data_files("get_indexes"):
|
||||
logging.info(filename)
|
||||
logging.debug(data)
|
||||
adapter._set_mock_data(data)
|
||||
|
||||
directory = adapter.get_directory("root")
|
||||
assert directory.id == "root"
|
||||
assert directory.parent_id is None
|
||||
assert len(directory.children) == 7
|
||||
child = directory.children[0]
|
||||
assert isinstance(child, SubsonicAPI.Directory)
|
||||
assert child.id == "73"
|
||||
assert child.name == "The Afters"
|
||||
assert child.parent_id == "root"
|
||||
|
||||
|
||||
def test_search(adapter: SubsonicAdapter):
|
||||
for filename, data in mock_data_files("search3"):
|
||||
logging.info(filename)
|
||||
logging.debug(data)
|
||||
adapter._set_mock_data(data)
|
||||
|
||||
search_results = adapter.search("3")
|
||||
assert len(search_results._songs) == 7
|
||||
assert len(search_results._artists) == 2
|
||||
assert len(search_results._albums) == 4
|
||||
|
Reference in New Issue
Block a user