python-plexapi/tests/test_library.py

848 lines
28 KiB
Python
Raw Normal View History

2017-01-09 14:21:54 +00:00
# -*- coding: utf-8 -*-
2021-03-19 22:53:20 +00:00
from collections import namedtuple
from datetime import datetime, timedelta
2021-08-04 21:02:01 +00:00
from urllib.parse import quote_plus
2017-01-09 14:08:18 +00:00
import pytest
import plexapi.base
2021-03-19 22:53:20 +00:00
from plexapi.exceptions import BadRequest, NotFound
2020-04-14 20:13:30 +00:00
from . import conftest as utils
2017-01-09 14:08:18 +00:00
2017-01-31 00:02:22 +00:00
def test_library_Library_section(plex):
sections = plex.library.sections()
2017-04-29 05:47:21 +00:00
assert len(sections) >= 3
2020-04-14 20:13:30 +00:00
section_name = plex.library.section("TV Shows")
assert section_name.title == "TV Shows"
2017-01-31 00:02:22 +00:00
with pytest.raises(NotFound):
2020-04-14 20:13:30 +00:00
assert plex.library.section("cant-find-me")
with pytest.raises(NotFound):
assert plex.library.sectionByID(-1)
2017-01-31 00:02:22 +00:00
def test_library_Library_sectionByID_is_equal_section(plex, movies):
# test that sectionByID refreshes the section if the key is missing
# this is needed if there isn't any cached sections
assert plex.library.sectionByID(movies.key).uuid == movies.uuid
def test_library_sectionByID_with_attrs(plex, movies):
assert movies.agent == "tv.plex.agents.movie"
2020-04-14 20:13:30 +00:00
# This seems to fail for some reason.
# my account allow of sync, didn't find any about settings about the library.
2021-03-19 22:53:20 +00:00
# assert movies.allowSync is ("sync" in plex.ownerFeatures)
2020-04-14 20:13:30 +00:00
assert movies.art == "/:/resources/movie-fanart.jpg"
assert utils.is_metadata(
movies.composite, prefix="/library/sections/", contains="/composite/"
)
assert utils.is_datetime(movies.createdAt)
2021-03-11 20:24:32 +00:00
assert movies.filters is True
2020-04-14 20:13:30 +00:00
assert movies._initpath == "/library/sections"
assert utils.is_int(movies.key)
assert movies.language == "en-US"
assert len(movies.locations) == 1
assert len(movies.locations[0]) >= 10
assert movies.refreshing is False
assert movies.scanner == "Plex Movie"
assert movies._server._baseurl == utils.SERVER_BASEURL
2020-04-14 20:13:30 +00:00
assert movies.thumb == "/:/resources/movie.png"
assert movies.title == "Movies"
assert movies.type == "movie"
assert utils.is_datetime(movies.updatedAt)
assert len(movies.uuid) == 36
2021-03-11 20:24:32 +00:00
def test_library_section_get_movie(movies):
assert movies.get("Sita Sings the Blues")
def test_library_MovieSection_getGuid(movies, movie):
result = movies.getGuid(guid=movie.guid)
assert result == movie
result = movies.getGuid(guid=movie.guids[0].id)
assert result == movie
with pytest.raises(NotFound):
movies.getGuid(guid='plex://movie/abcdefg')
with pytest.raises(NotFound):
movies.getGuid(guid='imdb://tt00000000')
def test_library_section_movies_all(movies):
assert movies.totalSize == 4
assert len(movies.all(container_start=0, container_size=1, maxresults=1)) == 1
def test_library_section_movies_all_guids(movies):
plexapi.base.USER_DONT_RELOAD_FOR_KEYS.add('guids')
try:
results = movies.all(includeGuids=False)
assert results[0].guids == []
results = movies.all()
assert results[0].guids
movie = movies.get("Sita Sings the Blues")
assert movie.guids
finally:
plexapi.base.USER_DONT_RELOAD_FOR_KEYS.remove('guids')
def test_library_section_totalDuration(tvshows):
assert utils.is_int(tvshows.totalDuration)
def test_library_section_totalStorage(tvshows):
assert utils.is_int(tvshows.totalStorage)
def test_library_section_totalViewSize(tvshows):
assert tvshows.totalViewSize() == 2
assert tvshows.totalViewSize(libtype="show") == 2
assert tvshows.totalViewSize(libtype="season") == 4
assert tvshows.totalViewSize(libtype="episode") == 49
show = tvshows.get("The 100")
show.addCollection("test_view_size")
assert tvshows.totalViewSize() == 3
assert tvshows.totalViewSize(includeCollections=False) == 2
show.removeCollection("test_view_size", locked=False)
2017-10-28 23:29:58 +00:00
def test_library_section_delete(movies, patched_http_call):
movies.delete()
def test_library_fetchItem(plex, movie):
item1 = plex.library.fetchItem(f"/library/metadata/{movie.ratingKey}")
item2 = plex.library.fetchItem(movie.ratingKey)
2020-04-14 20:13:30 +00:00
assert item1.title == "Elephants Dream"
assert item1 == item2 == movie
2017-01-09 14:21:54 +00:00
Improvements in tests process (#297) * lets begin * skip plexpass tests if there is not plexpass on account * test new myplex attrubutes * bootstrap: proper photos organisation * fix rest of photos tests * fix myplex new attributes test * fix music bootstrap by setting agent to lastfm * fix sync tests * increase bootstrap timeout * remove timeout from .travis.yml * do not create playlist-style photoalbums in plex-bootstraptest.py * allow negative filtering in LibrarySection.search() * fix sync tests once again * use sendCrashReports in test_settings * fix test_settings * fix test_video * do not accept eula in bootstrap * fix PlexServer.isLatest() * add test against old version of PlexServer * fix MyPlexAccount.OutOut * add flag for one-time testing in Travis * fix test_library onDeck tests * fix more tests * use tqdm in plex-bootstraptest for media scanning progress * create sections one-by-one * update docs on AlertListener for timeline entries * fix plex-bootstraptest for server version 1.3.2 * display skip/xpass/xfail reasons * fix tests on 1.3 * wait for music to be fully processed in plex-bootstraptest * fix misplaced TEST_ACCOUNT_ONCE * fix test_myplex_users, not sure if in proper-way * add pytest-rerunfailures; mark test_myplex_optout as flaky * fix comment * Revert "add pytest-rerunfailures; mark test_myplex_optout as flaky" This reverts commit 580e4c95a758c92329d757eb2f3fc3bf44b26f09. * restart plex container on failure * add conftest.wait_until() and used where some retries are required * add more wait_until() usage in test_sync * fix managed user search * fix updating managed users in myplex * allow to add new servers to existent users * add new server to a shared user while bootstrapping * add some docs on testing process * perform few attemps when unable to get the claim token * unlock websocket-client in requirements_dev * fix docblock in tools/plex-teardowntest * do not hardcode mediapart size in test_video * remove cache:pip from travis * Revert "unlock websocket-client in requirements_dev" This reverts commit 0d536bd06dbdc4a4b869a1686f8cd008898859fe. * remove debug from server.py * improve webhook tests * fix type() check to isinstance() * remove excessive `else` branch due to Hellowlol advice * add `unknown` as allowed `myPlexMappingState` in test_server
2018-09-14 18:03:23 +00:00
def test_library_onDeck(plex, movie):
movie.updateProgress(movie.duration // 4) # set progress to 25%
assert movie in plex.library.onDeck()
movie.markUnplayed()
2017-01-09 14:21:54 +00:00
def test_library_recentlyAdded(plex):
assert len(list(plex.library.recentlyAdded()))
2017-01-09 14:21:54 +00:00
def test_library_add_edit_delete(plex, movies, photos):
# Create Other Videos library = No external metadata scanning
2020-04-14 20:13:30 +00:00
section_name = "plexapi_test_section"
movie_location = movies.locations[0]
photo_location = photos.locations[0]
2020-04-14 20:13:30 +00:00
plex.library.add(
name=section_name,
type="movie",
agent="com.plexapp.agents.none",
scanner="Plex Video Files Scanner",
language="xn",
location=[movie_location, photo_location]
2020-04-14 20:13:30 +00:00
)
section = plex.library.section(section_name)
assert section.title == section_name
# Create library with an invalid path
error_section_name = "plexapi_error_section"
with pytest.raises(BadRequest):
plex.library.add(
name=error_section_name,
type="movie",
agent="com.plexapp.agents.none",
scanner="Plex Video Files Scanner",
language="xn",
location=[movie_location, photo_location[:-1]]
)
# Create library with no path
with pytest.raises(BadRequest):
plex.library.add(
name=error_section_name,
type="movie",
agent="com.plexapp.agents.none",
scanner="Plex Video Files Scanner",
language="xn",
)
2021-12-20 05:19:24 +00:00
with pytest.raises(NotFound):
plex.library.section(error_section_name)
new_title = "a renamed lib"
section.edit(name=new_title)
section.reload()
assert section.title == new_title
with pytest.raises(BadRequest):
section.addLocations(movie_location[:-1])
with pytest.raises(BadRequest):
section.removeLocations(movie_location[:-1])
section.removeLocations(photo_location)
section.reload()
assert len(section.locations) == 1
section.addLocations(photo_location)
section.reload()
assert len(section.locations) == 2
2022-01-06 03:53:41 +00:00
section.edit(**{'location': movie_location})
2022-01-05 03:47:06 +00:00
section.reload()
assert len(section.locations) == 1
2022-01-06 03:54:03 +00:00
with pytest.raises(BadRequest):
section.edit(**{'location': movie_location[:-1]})
2022-01-03 22:02:12 +00:00
# Attempt to remove all locations
with pytest.raises(BadRequest):
2022-01-05 14:14:34 +00:00
section.removeLocations(section.locations)
section.delete()
assert section not in plex.library.sections()
2017-02-27 22:16:02 +00:00
def test_library_Library_cleanBundle(plex):
plex.library.cleanBundles()
2017-02-02 04:47:22 +00:00
2017-01-31 00:02:22 +00:00
def test_library_Library_optimize(plex):
plex.library.optimize()
2017-01-31 00:02:22 +00:00
def test_library_Library_emptyTrash(plex):
plex.library.emptyTrash()
2017-02-02 04:47:22 +00:00
2017-01-31 00:02:22 +00:00
def _test_library_Library_refresh(plex):
# TODO: fix mangle and proof the sections attrs
plex.library.refresh()
2017-02-02 04:47:22 +00:00
2017-01-31 00:02:22 +00:00
def test_library_Library_update(plex):
plex.library.update()
def test_library_Library_cancelUpdate(plex):
plex.library.cancelUpdate()
2017-01-31 00:02:22 +00:00
def test_library_Library_deleteMediaPreviews(plex):
plex.library.deleteMediaPreviews()
2017-01-31 00:02:22 +00:00
2017-10-28 23:29:58 +00:00
def test_library_Library_all(plex):
2020-04-14 20:13:30 +00:00
assert len(plex.library.all(title__iexact="The 100"))
2017-10-28 23:29:58 +00:00
def test_library_Library_search(plex):
2020-04-14 20:13:30 +00:00
item = plex.library.search("Elephants Dream")[0]
assert item.title == "Elephants Dream"
assert len(plex.library.search(libtype="episode"))
2017-01-31 00:02:22 +00:00
def test_library_Library_tags(plex):
tags = plex.library.tags('genre')
assert len(tags)
with pytest.raises(NotFound):
plex.library.tags('unknown')
def test_library_MovieSection_update(movies):
movies.update()
2020-11-21 01:16:08 +00:00
def test_library_MovieSection_update_path(movies):
movies.update(path=movies.locations[0])
2017-10-28 23:29:58 +00:00
def test_library_MovieSection_refresh(movies, patched_http_call):
movies.refresh()
2017-10-25 21:43:23 +00:00
def test_library_MovieSection_search_genre(movie, movies):
genre = movie.genres[0]
assert len(movies.search(genre=genre)) >= 1
2017-10-25 21:43:23 +00:00
def test_library_MovieSection_cancelUpdate(movies):
movies.cancelUpdate()
def test_library_deleteMediaPreviews(movies):
movies.deleteMediaPreviews()
Improvements in tests process (#297) * lets begin * skip plexpass tests if there is not plexpass on account * test new myplex attrubutes * bootstrap: proper photos organisation * fix rest of photos tests * fix myplex new attributes test * fix music bootstrap by setting agent to lastfm * fix sync tests * increase bootstrap timeout * remove timeout from .travis.yml * do not create playlist-style photoalbums in plex-bootstraptest.py * allow negative filtering in LibrarySection.search() * fix sync tests once again * use sendCrashReports in test_settings * fix test_settings * fix test_video * do not accept eula in bootstrap * fix PlexServer.isLatest() * add test against old version of PlexServer * fix MyPlexAccount.OutOut * add flag for one-time testing in Travis * fix test_library onDeck tests * fix more tests * use tqdm in plex-bootstraptest for media scanning progress * create sections one-by-one * update docs on AlertListener for timeline entries * fix plex-bootstraptest for server version 1.3.2 * display skip/xpass/xfail reasons * fix tests on 1.3 * wait for music to be fully processed in plex-bootstraptest * fix misplaced TEST_ACCOUNT_ONCE * fix test_myplex_users, not sure if in proper-way * add pytest-rerunfailures; mark test_myplex_optout as flaky * fix comment * Revert "add pytest-rerunfailures; mark test_myplex_optout as flaky" This reverts commit 580e4c95a758c92329d757eb2f3fc3bf44b26f09. * restart plex container on failure * add conftest.wait_until() and used where some retries are required * add more wait_until() usage in test_sync * fix managed user search * fix updating managed users in myplex * allow to add new servers to existent users * add new server to a shared user while bootstrapping * add some docs on testing process * perform few attemps when unable to get the claim token * unlock websocket-client in requirements_dev * fix docblock in tools/plex-teardowntest * do not hardcode mediapart size in test_video * remove cache:pip from travis * Revert "unlock websocket-client in requirements_dev" This reverts commit 0d536bd06dbdc4a4b869a1686f8cd008898859fe. * remove debug from server.py * improve webhook tests * fix type() check to isinstance() * remove excessive `else` branch due to Hellowlol advice * add `unknown` as allowed `myPlexMappingState` in test_server
2018-09-14 18:03:23 +00:00
def test_library_MovieSection_onDeck(movie, movies, tvshows, episode):
movie.updateProgress(movie.duration // 4) # set progress to 25%
assert movie in movies.onDeck()
movie.markUnplayed()
episode.updateProgress(episode.duration // 4)
assert episode in tvshows.onDeck()
episode.markUnplayed()
2017-01-31 00:02:22 +00:00
2021-03-19 23:27:28 +00:00
def test_library_MovieSection_searchMovies(movies):
assert movies.searchMovies(title="Elephants Dream")
def test_library_MovieSection_recentlyAdded(movies, movie):
assert movie in movies.recentlyAdded()
assert movie in movies.recentlyAddedMovies()
2017-01-31 00:02:22 +00:00
def test_library_MovieSection_analyze(movies):
movies.analyze()
2017-01-31 00:02:22 +00:00
def test_library_MovieSection_collections(movies, movie):
2021-05-28 03:06:53 +00:00
try:
collection = movies.createCollection("test_library_MovieSection_collections", movie)
collections = movies.collections()
assert len(collections)
assert collection in collections
c = movies.collection(collection.title)
assert collection == c
2021-05-28 03:06:53 +00:00
finally:
collection.delete()
2021-05-28 03:06:53 +00:00
def test_library_MovieSection_collection_exception(movies):
with pytest.raises(NotFound):
movies.collection("Does Not Exists")
@pytest.mark.authenticated
def test_library_MovieSection_managedHubs(movies):
recommendations = movies.managedHubs()
with pytest.raises(BadRequest):
recommendations[0].remove()
first = recommendations[0]
first.promoteRecommended().promoteHome().promoteShared()
assert first.promotedToRecommended is True
assert first.promotedToOwnHome is True
assert first.promotedToSharedHome is True
first.demoteRecommended().demoteHome().demoteShared()
assert first.promotedToRecommended is False
assert first.promotedToOwnHome is False
assert first.promotedToSharedHome is False
last = recommendations[-1]
last.move()
recommendations = movies.managedHubs()
assert first.identifier == recommendations[1].identifier
assert last.identifier == recommendations[0].identifier
last.move(after=first)
recommendations = movies.managedHubs()
assert first.identifier == recommendations[0].identifier
assert last.identifier == recommendations[1].identifier
movies.resetManagedHubs()
recommendations = movies.managedHubs()
assert first.identifier == recommendations[0].identifier
assert last.identifier == recommendations[-1].identifier
2021-08-04 21:02:01 +00:00
def test_library_MovieSection_PlexWebURL(plex, movies):
tab = 'library'
url = movies.getWebURL(tab=tab)
assert url.startswith('https://app.plex.tv/desktop')
assert plex.machineIdentifier in url
assert f'source={movies.key}' in url
assert f'pivot={tab}' in url
2021-08-04 21:02:01 +00:00
# Test a different base
base = 'https://doesnotexist.com/plex'
url = movies.getWebURL(base=base)
assert url.startswith(base)
def test_library_MovieSection_PlexWebURL_hub(plex, movies):
hubs = movies.hubs()
hub = next(iter(hubs), None)
assert hub is not None
2021-09-26 22:23:18 +00:00
url = hub.section().getWebURL(key=hub.key)
2021-08-04 21:02:01 +00:00
assert url.startswith('https://app.plex.tv/desktop')
assert plex.machineIdentifier in url
assert f'source={movies.key}' in url
2021-08-04 21:02:01 +00:00
assert quote_plus(hub.key) in url
2021-03-19 23:27:28 +00:00
def test_library_ShowSection_all(tvshows):
assert len(tvshows.all(title__iexact="The 100"))
def test_library_ShowSection_searchShows(tvshows):
2020-04-14 20:13:30 +00:00
assert tvshows.searchShows(title="The 100")
2017-01-31 00:02:22 +00:00
def test_library_ShowSection_searchSeasons(tvshows):
2021-03-19 23:36:50 +00:00
assert tvshows.searchSeasons(**{"show.title": "The 100"})
2021-03-19 23:27:28 +00:00
def test_library_ShowSection_searchEpisodes(tvshows):
2020-04-14 20:13:30 +00:00
assert tvshows.searchEpisodes(title="Winter Is Coming")
2017-01-31 00:02:22 +00:00
def test_library_ShowSection_recentlyAdded(tvshows, show):
season = show.season(1)
episode = season.episode(1)
assert show in tvshows.recentlyAdded()
assert show in tvshows.recentlyAddedShows()
assert season in tvshows.recentlyAddedSeasons()
assert episode in tvshows.recentlyAddedEpisodes()
2017-01-31 00:02:22 +00:00
def test_library_ShowSection_playlists(tvshows, show):
episodes = show.episodes()
try:
playlist = tvshows.createPlaylist("test_library_ShowSection_playlists", episodes[:3])
playlists = tvshows.playlists()
assert len(playlists)
assert playlist in playlists
p = tvshows.playlist(playlist.title)
assert playlist == p
playlists = tvshows.playlists(title="test_", sort="mediaCount:asc")
assert playlist in playlists
finally:
playlist.delete()
def test_library_ShowSection_playlist_exception(tvshows):
with pytest.raises(NotFound):
tvshows.playlist("Does Not Exists")
def test_library_MusicSection_albums(music):
assert len(music.albums())
2017-01-31 00:02:22 +00:00
def test_library_MusicSection_stations(music):
assert len(music.stations())
2021-09-16 22:23:59 +00:00
def test_library_MusicSection_searchArtists(music):
assert len(music.searchArtists(title="Broke for Free"))
2017-01-31 00:02:22 +00:00
def test_library_MusicSection_searchAlbums(music):
2020-04-29 21:23:22 +00:00
assert len(music.searchAlbums(title="Layers"))
2017-01-31 00:02:22 +00:00
2021-09-16 22:23:59 +00:00
def test_library_MusicSection_searchTracks(music):
assert len(music.searchTracks(title="As Colourful As Ever"))
def test_library_MusicSection_recentlyAdded(music, artist):
album = artist.albums()[0]
track = album.tracks()[0]
assert artist in music.recentlyAdded()
assert artist in music.recentlyAddedArtists()
assert album in music.recentlyAddedAlbums()
assert track in music.recentlyAddedTracks()
2017-04-23 05:54:53 +00:00
def test_library_PhotoSection_searchAlbums(photos, photoalbum):
title = photoalbum.title
albums = photos.searchAlbums(title)
2017-01-31 00:02:22 +00:00
assert len(albums)
2017-04-23 05:54:53 +00:00
def test_library_PhotoSection_searchPhotos(photos, photoalbum):
title = photoalbum.photos()[0].title
assert len(photos.searchPhotos(title))
2017-01-31 00:02:22 +00:00
def test_library_PhotoSection_recentlyAdded(photos, photoalbum):
assert photoalbum in photos.recentlyAddedAlbums()
2021-03-11 20:24:32 +00:00
def test_library_and_section_search_for_movie(plex, movies):
find = "Elephants Dream"
l_search = plex.library.search(find)
2021-03-11 20:24:32 +00:00
s_search = movies.search(find)
2017-01-09 14:21:54 +00:00
assert l_search == s_search
2020-06-18 13:57:04 +00:00
def test_library_settings(movies):
settings = movies.settings()
assert len(settings) >= 1
def test_library_editAdvanced_default(movies):
movies.editAdvanced(hidden=2)
for setting in movies.settings():
2021-03-19 22:53:20 +00:00
if setting.id == "hidden":
assert int(setting.value) == 2
movies.editAdvanced(collectionMode=0)
for setting in movies.settings():
2021-03-19 22:53:20 +00:00
if setting.id == "collectionMode":
assert int(setting.value) == 0
movies.defaultAdvanced()
for setting in movies.settings():
assert str(setting.value) == str(setting.default)
def test_library_lockUnlockAllFields(movies):
for movie in movies.all():
assert 'thumb' not in [f.name for f in movie.fields]
movies.lockAllField('thumb')
for movie in movies.all():
assert 'thumb' in [f.name for f in movie.fields]
movies.unlockAllField('thumb')
for movie in movies.all():
assert 'thumb' not in [f.name for f in movie.fields]
2021-03-11 20:24:32 +00:00
def test_search_with_weird_a(plex, tvshows):
2020-04-15 22:30:00 +00:00
ep_title = "Coup de Grâce"
result_root = plex.search(ep_title)
2021-03-11 20:24:32 +00:00
result_shows = tvshows.searchEpisodes(title=ep_title)
2017-02-07 07:14:49 +00:00
assert result_root
2017-01-09 14:21:54 +00:00
assert result_shows
2017-02-07 07:14:49 +00:00
assert result_root == result_shows
2017-01-09 14:08:18 +00:00
2021-03-11 20:24:32 +00:00
def test_crazy_search(plex, movies, movie):
2020-04-14 20:13:30 +00:00
assert movie in movies.search(
actor=movie.actors[0], sort="titleSort"
), "Unable to search movie by actor."
assert movie in movies.search(
director=movie.directors[0]
), "Unable to search movie by director."
assert movie in movies.search(
year=["2006", "2007"]
), "Unable to search movie by year."
assert movie not in movies.search(year=2007), "Unable to filter movie by year."
assert movie in movies.search(actor=movie.actors[0].tag)
2020-04-27 18:43:35 +00:00
assert len(movies.search(container_start=2, maxresults=1)) == 1
assert len(movies.search(container_size=None)) == 4
assert len(movies.search(container_size=1)) == 4
assert len(movies.search(container_start=9999, container_size=1)) == 0
assert len(movies.search(container_start=2, container_size=1)) == 2
2021-03-11 20:24:32 +00:00
def test_library_section_timeline(plex, movies):
tl = movies.timeline()
assert tl.TAG == "LibraryTimeline"
assert tl.size > 0
assert tl.allowSync is False
assert tl.art == "/:/resources/movie-fanart.jpg"
assert tl.content == "secondary"
assert tl.identifier == "com.plexapp.plugins.library"
assert datetime.fromtimestamp(tl.latestEntryTime).date() == datetime.today().date()
assert tl.mediaTagPrefix == "/system/bundle/media/flags/"
assert tl.mediaTagVersion > 1
assert tl.thumb == "/:/resources/movie.png"
assert tl.title1 == "Movies"
2021-01-31 00:25:08 +00:00
assert utils.is_int(tl.updateQueueSize, gte=0)
assert tl.viewGroup == "secondary"
assert tl.viewMode == 65592
2021-03-19 22:53:20 +00:00
2021-03-19 23:27:28 +00:00
def test_library_MovieSection_hubSearch(movies):
assert movies.hubSearch("Elephants Dream")
2021-05-21 03:16:20 +00:00
def test_library_MovieSection_search(movies, movie, collection):
2021-03-19 22:53:20 +00:00
movie.addLabel("test_search")
movie.addCollection("test_search")
_test_library_search(movies, movie)
movie.removeLabel("test_search", locked=False)
movie.removeCollection("test_search", locked=False)
2021-05-21 03:16:20 +00:00
_test_library_search(movies, collection)
2021-03-19 22:53:20 +00:00
def test_library_MovieSection_search_FilterChoice(movies, collection):
filterChoice = next(c for c in movies.listFilterChoices("collection") if c.title == collection.title)
results = movies.search(filters={'collection': filterChoice})
movie = collection.items()[0]
assert movie in results
def test_library_MovieSection_advancedSearch(movies, movie):
advancedFilters = {
'and': [
{
'or': [
{'title': 'elephant'},
{'title': 'bunny'}
]
},
{'year>>': 1990},
{'unwatched': True}
]
}
results = movies.search(filters=advancedFilters)
assert movie in results
2021-05-28 04:19:40 +00:00
results = movies.search(limit=1)
assert len(results) == 1
2021-03-19 22:53:20 +00:00
def test_library_ShowSection_search(tvshows, show):
show.addLabel("test_search")
show.addCollection("test_search")
_test_library_search(tvshows, show)
show.removeLabel("test_search", locked=False)
show.removeCollection("test_search", locked=False)
season = show.season(season=1)
_test_library_search(tvshows, season)
episode = season.episode(episode=1)
_test_library_search(tvshows, episode)
# Additional test for mapping field to the correct libtype
assert tvshows.search(unwatched=True) # equal to episode.unwatched=True
2021-03-19 22:53:20 +00:00
def test_library_MusicSection_search(music, artist):
artist.addGenre("test_search")
artist.addStyle("test_search")
artist.addMood("test_search")
artist.addCollection("test_search")
_test_library_search(music, artist)
artist.removeGenre("test_search", locked=False)
artist.removeStyle("test_search", locked=False)
artist.removeMood("test_search", locked=False)
artist.removeCollection("test_search", locked=False)
album = artist.album("Layers")
album.addGenre("test_search")
album.addStyle("test_search")
album.addMood("test_search")
album.addCollection("test_search")
album.addLabel("test_search")
_test_library_search(music, album)
album.removeGenre("test_search", locked=False)
album.removeStyle("test_search", locked=False)
album.removeMood("test_search", locked=False)
album.removeCollection("test_search", locked=False)
album.removeLabel("test_search", locked=False)
track = album.track(track=1)
track.addMood("test_search")
_test_library_search(music, track)
track.removeMood("test_search", locked=False)
def test_library_PhotoSection_search(photos, photoalbum):
photo = photoalbum.photo("photo1")
photo.addTag("test_search")
_test_library_search(photos, photo)
photo.removeTag("test_search")
def test_library_MovieSection_search_sort(movies):
results = movies.search(sort="titleSort")
titleSort = [r.titleSort for r in results]
assert titleSort == sorted(titleSort)
2021-03-19 22:53:20 +00:00
results_asc = movies.search(sort="titleSort:asc")
titleSort_asc = [r.titleSort for r in results_asc]
assert titleSort == titleSort_asc
2021-03-19 22:53:20 +00:00
results_desc = movies.search(sort="titleSort:desc")
titleSort_desc = [r.titleSort for r in results_desc]
assert titleSort_desc == sorted(titleSort_desc, reverse=True)
# Test manually added sorts
results_guid = movies.search(sort="guid")
guid_asc = [r.guid for r in results_guid]
assert guid_asc == sorted(guid_asc)
results_summary = movies.search(sort="summary")
summary_asc = [r.summary for r in results_summary]
assert summary_asc == sorted(summary_asc)
results_tagline = movies.search(sort="tagline")
tagline_asc = [r.tagline for r in results_tagline if r.tagline]
assert tagline_asc == sorted(tagline_asc)
results_updatedAt = movies.search(sort="updatedAt")
updatedAt_asc = [r.updatedAt for r in results_updatedAt]
assert updatedAt_asc == sorted(updatedAt_asc)
# Test multi-sort
results_multi_str = movies.search(sort="year:asc,titleSort:asc")
titleSort_multi_str = [(r.year, r.titleSort) for r in results_multi_str]
assert titleSort_multi_str == sorted(titleSort_multi_str)
results_multi_list = movies.search(sort=["year:desc", "titleSort:desc"])
titleSort_multi_list = [(r.year, r.titleSort) for r in results_multi_list]
assert titleSort_multi_list == sorted(titleSort_multi_list, reverse=True)
# Test sort using FilteringSort object
sortObj = next(s for s in movies.listSorts() if s.key == "year")
results_sortObj = movies.search(sort=sortObj)
sortObj_list = [r.year for r in results_sortObj]
assert sortObj_list == sorted(sortObj_list, reverse=True)
2021-03-19 22:53:20 +00:00
def test_library_ShowSection_search_sort(tvshows):
# Test predefined Plex multi-sort
2021-05-16 06:12:39 +00:00
seasonAsc = "season.index,season.titleSort"
results = tvshows.search(sort=seasonAsc, libtype="season")
sortedResults = sorted(results, key=lambda s: (s.index, s.titleSort))
assert results == sortedResults
2021-05-16 06:12:39 +00:00
seasonShowAsc = "show.titleSort,index"
results = tvshows.search(sort=seasonShowAsc, libtype="season")
sortedResults = sorted(results, key=lambda s: (s.show().titleSort, s.index))
assert results == sortedResults
episodeShowAsc = (
2021-05-16 06:12:39 +00:00
"show.titleSort,season.index:nullsLast,episode.index:nullsLast,"
"episode.originallyAvailableAt:nullsLast,episode.titleSort,episode.id"
)
2021-05-16 06:12:39 +00:00
results = tvshows.search(sort=episodeShowAsc, libtype="episode")
sortedResults = sorted(
results,
key=lambda e: (
e.show().titleSort, e.season().index, e.index,
e.originallyAvailableAt, e.titleSort, e.ratingKey)
)
assert results == sortedResults
episodeShowDesc = (
2021-05-16 06:12:39 +00:00
"show.titleSort:desc,season.index:nullsLast,episode.index:nullsLast,"
"episode.originallyAvailableAt:nullsLast,episode.titleSort,episode.id"
)
2021-05-16 06:12:39 +00:00
results = tvshows.search(sort=episodeShowDesc, libtype="episode")
sortedResults = sorted(
sorted(
results,
key=lambda e: (
e.season().index, e.index,
e.originallyAvailableAt, e.titleSort, e.ratingKey)
),
key=lambda e: e.show().titleSort,
reverse=True
)
assert results == sortedResults
# Test manually added sorts
2021-05-16 06:12:39 +00:00
results_index = tvshows.search(sort="show.index,season.index,episode.index", libtype="episode")
index_asc = [(r.show().index, r.season().index, r.index) for r in results_index]
assert index_asc == sorted(index_asc)
def test_library_MusicSection_search_sort(music):
# Test predefined Plex multi-sort
2021-05-16 06:12:39 +00:00
albumArtistAsc = "artist.titleSort,album.titleSort,album.index,album.id,album.originallyAvailableAt"
results = music.search(sort=albumArtistAsc, libtype="album")
sortedResults = sorted(
results,
key=lambda a: (
a.artist().titleSort, a.titleSort, a.index, a.ratingKey, a.originallyAvailableAt
)
)
assert results == sortedResults
trackAlbumArtistAsc = (
2021-05-16 06:12:39 +00:00
"artist.titleSort,album.titleSort,album.year,"
"track.absoluteIndex,track.index,track.titleSort,track.id"
)
2021-05-16 06:12:39 +00:00
results = music.search(sort=trackAlbumArtistAsc, libtype="track")
sortedResults = sorted(
results,
key=lambda t: (
t.artist().titleSort, t.album().titleSort, t.album().year,
t.index, t.titleSort, t.ratingKey # Skip unknown absoluteIndex
)
)
assert results == sortedResults
2021-03-19 22:53:20 +00:00
def test_library_search_exceptions(movies):
with pytest.raises(BadRequest):
movies.listFilterChoices(field="123abc.title")
with pytest.raises(BadRequest):
movies.search(**{"123abc": True})
with pytest.raises(BadRequest):
movies.search(year="123abc")
with pytest.raises(BadRequest):
movies.search(sort="123abc")
with pytest.raises(BadRequest):
movies.search(filters=[])
with pytest.raises(BadRequest):
movies.search(filters={'and': {'title': 'test'}})
with pytest.raises(BadRequest):
movies.search(filters={'and': [], 'title': 'test'})
2021-03-19 22:53:20 +00:00
with pytest.raises(NotFound):
2021-05-16 06:12:39 +00:00
movies.getFilterType(libtype="show")
2021-03-19 22:53:20 +00:00
with pytest.raises(NotFound):
movies.getFieldType(fieldType="unknown")
with pytest.raises(NotFound):
movies.listFilterChoices(field="unknown")
with pytest.raises(NotFound):
2021-03-19 23:27:28 +00:00
movies.search(unknown="unknown")
2021-03-19 22:53:20 +00:00
with pytest.raises(NotFound):
movies.search(**{"title<>!=": "unknown"})
with pytest.raises(NotFound):
movies.search(sort="unknown")
with pytest.raises(NotFound):
movies.search(sort="titleSort:bad")
def _test_library_search(library, obj):
# Create & operator
2021-05-16 06:12:39 +00:00
AndOperator = namedtuple("AndOperator", ["key", "title"])
andOp = AndOperator("&=", "and")
2021-03-19 22:53:20 +00:00
fields = library.listFields(obj.type)
for field in fields:
fieldAttr = field.key.split(".")[-1]
operators = library.listOperators(field.type)
2021-05-16 06:12:39 +00:00
if field.type in {"tag", "string"}:
2021-03-19 22:53:20 +00:00
operators += [andOp]
for operator in operators:
2021-05-24 04:08:09 +00:00
if (
fieldAttr == "unmatched" and operator.key == "!="
or fieldAttr in {"audienceRating", "rating"} and operator.key in {"=", "!="}
or fieldAttr == "userRating"
):
2021-03-19 22:53:20 +00:00
continue
value = getattr(obj, fieldAttr, None)
if field.type == "boolean" and value is None:
value = fieldAttr.startswith("unwatched")
2021-05-16 06:12:39 +00:00
if field.type == "tag" and isinstance(value, list) and value and operator.title != "and":
2021-03-19 22:53:20 +00:00
value = value[0]
elif value is None:
continue
if operator.title == "begins with":
searchValue = value[:3]
elif operator.title == "ends with":
searchValue = value[-3:]
elif "contain" in operator.title:
searchValue = value.split(" ")[0]
elif operator.title == "is less than":
searchValue = value + 1
elif operator.title == "is greater than":
searchValue = max(value - 1, 1)
2021-03-19 22:53:20 +00:00
elif operator.title == "is before":
searchValue = value + timedelta(days=1)
elif operator.title == "is after":
searchValue = value - timedelta(days=1)
else:
searchValue = value
_do_test_library_search(library, obj, field, operator, searchValue)
2021-03-19 22:53:20 +00:00
# Test search again using string tag and date
if field.type == "tag" and fieldAttr != "contentRating":
if not isinstance(searchValue, list):
searchValue = [searchValue]
searchValue = [v.tag for v in searchValue]
_do_test_library_search(library, obj, field, operator, searchValue)
elif field.type == "date":
searchValue = searchValue.strftime("%Y-%m-%d")
_do_test_library_search(library, obj, field, operator, searchValue)
searchValue = "1s"
_do_test_library_search(library, obj, field, operator, searchValue)
def _do_test_library_search(library, obj, field, operator, searchValue):
searchFilter = {field.key + operator.key[:-1]: searchValue}
results = library.search(libtype=obj.type, filters=searchFilter)
2021-05-16 06:12:39 +00:00
if operator.key.startswith("!") or operator.key.startswith(">>") and (searchValue == 1 or searchValue == "1s"):
assert obj not in results
else:
assert obj in results