2017-01-09 04:40:39 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2017-10-25 19:53:52 +00:00
|
|
|
import pytest
|
2022-11-08 23:57:54 +00:00
|
|
|
from plexapi.exceptions import BadRequest, NotFound, Unauthorized
|
2021-11-21 00:16:01 +00:00
|
|
|
from plexapi.myplex import MyPlexInvite
|
2020-04-29 21:23:22 +00:00
|
|
|
|
2017-10-26 23:09:17 +00:00
|
|
|
from . import conftest as utils
|
2021-11-21 00:16:01 +00:00
|
|
|
from .payloads import MYPLEX_INVITE
|
2017-01-09 04:40:39 +00:00
|
|
|
|
2017-04-15 00:47:59 +00:00
|
|
|
|
|
|
|
def test_myplex_accounts(account, plex):
|
2020-04-29 21:23:22 +00:00
|
|
|
assert account, "Must specify username, password & resource to run this test."
|
|
|
|
print("MyPlexAccount:")
|
2022-08-28 05:56:01 +00:00
|
|
|
print(f"username: {account.username}")
|
|
|
|
print(f"email: {account.email}")
|
|
|
|
print(f"home: {account.home}")
|
2020-04-29 21:23:22 +00:00
|
|
|
assert account.username, "Account has no username"
|
|
|
|
assert account.authenticationToken, "Account has no authenticationToken"
|
|
|
|
assert account.email, "Account has no email"
|
|
|
|
assert account.home is not None, "Account has no home"
|
2017-04-15 00:47:59 +00:00
|
|
|
account = plex.account()
|
2020-04-29 21:23:22 +00:00
|
|
|
print("Local PlexServer.account():")
|
2022-08-28 05:56:01 +00:00
|
|
|
print(f"username: {account.username}")
|
2020-04-29 21:23:22 +00:00
|
|
|
# print('authToken: %s' % account.authToken)
|
2022-08-28 05:56:01 +00:00
|
|
|
print(f"signInState: {account.signInState}")
|
2020-04-29 21:23:22 +00:00
|
|
|
assert account.username, "Account has no username"
|
|
|
|
assert account.authToken, "Account has no authToken"
|
|
|
|
assert account.signInState, "Account has no signInState"
|
2017-01-09 04:40:39 +00:00
|
|
|
|
|
|
|
|
2017-04-15 00:47:59 +00:00
|
|
|
def test_myplex_resources(account):
|
2020-04-29 21:23:22 +00:00
|
|
|
assert account, "Must specify username, password & resource to run this test."
|
2017-01-09 04:40:39 +00:00
|
|
|
resources = account.resources()
|
|
|
|
for resource in resources:
|
2020-04-29 21:23:22 +00:00
|
|
|
name = resource.name or "Unknown"
|
2017-01-09 04:40:39 +00:00
|
|
|
connections = [c.uri for c in resource.connections]
|
2020-04-29 21:23:22 +00:00
|
|
|
connections = ", ".join(connections) if connections else "None"
|
2022-08-28 05:56:01 +00:00
|
|
|
print(f"{name} ({resource.product}): {connections}")
|
2023-07-27 21:30:09 +00:00
|
|
|
assert resources, f"No resources found for account: {account.username}"
|
2017-01-09 04:40:39 +00:00
|
|
|
|
2017-02-02 04:10:12 +00:00
|
|
|
|
2017-04-24 02:59:22 +00:00
|
|
|
def test_myplex_connect_to_resource(plex, account):
|
|
|
|
servername = plex.friendlyName
|
2017-04-15 00:47:59 +00:00
|
|
|
for resource in account.resources():
|
2017-04-24 02:59:22 +00:00
|
|
|
if resource.name == servername:
|
2017-02-02 04:10:12 +00:00
|
|
|
break
|
2017-04-24 02:59:22 +00:00
|
|
|
assert resource.connect(timeout=10)
|
2017-02-02 04:10:12 +00:00
|
|
|
|
|
|
|
|
2017-04-15 00:47:59 +00:00
|
|
|
def test_myplex_devices(account):
|
2017-01-09 04:40:39 +00:00
|
|
|
devices = account.devices()
|
|
|
|
for device in devices:
|
2020-04-29 21:23:22 +00:00
|
|
|
name = device.name or "Unknown"
|
|
|
|
connections = ", ".join(device.connections) if device.connections else "None"
|
2022-08-28 05:56:01 +00:00
|
|
|
print(f"{name} ({device.product}): {connections}")
|
|
|
|
assert devices, f"No devices found for account: {account.name}"
|
2017-01-09 04:40:39 +00:00
|
|
|
|
|
|
|
|
2018-09-14 18:03:23 +00:00
|
|
|
def test_myplex_device(account, plex):
|
|
|
|
assert account.device(plex.friendlyName)
|
2017-10-25 19:53:52 +00:00
|
|
|
|
|
|
|
|
2017-04-15 00:47:59 +00:00
|
|
|
def _test_myplex_connect_to_device(account):
|
2017-01-09 04:40:39 +00:00
|
|
|
devices = account.devices()
|
|
|
|
for device in devices:
|
2020-04-29 21:23:22 +00:00
|
|
|
if device.name == "some client name" and len(device.connections):
|
2017-01-09 04:40:39 +00:00
|
|
|
break
|
|
|
|
client = device.connect()
|
2020-04-29 21:23:22 +00:00
|
|
|
assert client, "Unable to connect to device"
|
2017-02-02 04:10:12 +00:00
|
|
|
|
|
|
|
|
2017-04-15 00:47:59 +00:00
|
|
|
def test_myplex_users(account):
|
2017-02-02 04:10:12 +00:00
|
|
|
users = account.users()
|
2018-09-14 18:03:23 +00:00
|
|
|
if not len(users):
|
2020-04-29 21:23:22 +00:00
|
|
|
return pytest.skip("You have to add a shared account into your MyPlex")
|
2022-08-28 05:56:01 +00:00
|
|
|
print(f"Found {len(users)} users.")
|
2017-04-15 00:47:59 +00:00
|
|
|
user = account.user(users[0].title)
|
2022-08-28 05:56:01 +00:00
|
|
|
print(f"Found user: {user}")
|
|
|
|
assert user, f"Could not find user {users[0].title}"
|
2017-10-25 19:53:52 +00:00
|
|
|
|
2023-08-27 21:24:54 +00:00
|
|
|
try:
|
|
|
|
users[0].servers[0]
|
|
|
|
except IndexError:
|
|
|
|
return pytest.skip(f"{users[0].title} shared user does not have access to any servers")
|
|
|
|
|
2020-04-29 21:23:22 +00:00
|
|
|
assert (
|
|
|
|
len(users[0].servers[0].sections()) > 0
|
|
|
|
), "Couldn't info about the shared libraries"
|
2017-10-25 20:07:06 +00:00
|
|
|
|
2017-10-25 19:53:52 +00:00
|
|
|
|
2018-09-14 18:03:23 +00:00
|
|
|
def test_myplex_resource(account, plex):
|
|
|
|
assert account.resource(plex.friendlyName)
|
2017-10-25 19:53:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_myplex_webhooks(account):
|
2018-09-14 18:03:23 +00:00
|
|
|
if account.subscriptionActive:
|
|
|
|
assert isinstance(account.webhooks(), list)
|
|
|
|
else:
|
|
|
|
with pytest.raises(BadRequest):
|
|
|
|
account.webhooks()
|
2017-10-25 19:53:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_myplex_addwebhooks(account):
|
2018-09-14 18:03:23 +00:00
|
|
|
if account.subscriptionActive:
|
2020-04-29 21:23:22 +00:00
|
|
|
assert "http://example.com" in account.addWebhook("http://example.com")
|
2018-09-14 18:03:23 +00:00
|
|
|
else:
|
|
|
|
with pytest.raises(BadRequest):
|
2020-04-29 21:23:22 +00:00
|
|
|
account.addWebhook("http://example.com")
|
2017-10-25 19:53:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_myplex_deletewebhooks(account):
|
2018-09-14 18:03:23 +00:00
|
|
|
if account.subscriptionActive:
|
2020-04-29 21:23:22 +00:00
|
|
|
assert "http://example.com" not in account.deleteWebhook("http://example.com")
|
2018-09-14 18:03:23 +00:00
|
|
|
else:
|
|
|
|
with pytest.raises(BadRequest):
|
2020-04-29 21:23:22 +00:00
|
|
|
account.deleteWebhook("http://example.com")
|
2017-10-25 19:53:52 +00:00
|
|
|
|
|
|
|
|
2018-09-14 18:03:23 +00:00
|
|
|
def test_myplex_optout(account_once):
|
2017-10-25 19:53:52 +00:00
|
|
|
def enabled():
|
2020-04-29 21:23:22 +00:00
|
|
|
ele = account_once.query("https://plex.tv/api/v2/user/privacy")
|
|
|
|
lib = ele.attrib.get("optOutLibraryStats")
|
|
|
|
play = ele.attrib.get("optOutPlayback")
|
2017-10-25 19:53:52 +00:00
|
|
|
return bool(int(lib)), bool(int(play))
|
|
|
|
|
2018-09-14 18:03:23 +00:00
|
|
|
account_once.optOut(library=True, playback=True)
|
|
|
|
utils.wait_until(lambda: enabled() == (True, True))
|
|
|
|
account_once.optOut(library=False, playback=False)
|
|
|
|
utils.wait_until(lambda: enabled() == (False, False))
|
2017-10-25 19:53:52 +00:00
|
|
|
|
2017-10-26 23:09:17 +00:00
|
|
|
|
2021-05-31 13:56:37 +00:00
|
|
|
@pytest.mark.authenticated
|
2021-06-07 01:39:45 +00:00
|
|
|
@pytest.mark.xfail(reason="Test account is missing online media sources?")
|
2020-08-30 06:20:30 +00:00
|
|
|
def test_myplex_onlineMediaSources_optOut(account):
|
2021-06-07 00:03:08 +00:00
|
|
|
onlineMediaSources = account.onlineMediaSources()
|
|
|
|
for optOut in onlineMediaSources:
|
|
|
|
if optOut.key == 'tv.plex.provider.news':
|
|
|
|
# News is no longer available
|
|
|
|
continue
|
|
|
|
|
|
|
|
optOutValue = optOut.value
|
|
|
|
optOut.optIn()
|
|
|
|
assert optOut.value == 'opt_in'
|
|
|
|
optOut.optOut()
|
|
|
|
assert optOut.value == 'opt_out'
|
|
|
|
if optOut.key == 'tv.plex.provider.music':
|
|
|
|
with pytest.raises(BadRequest):
|
|
|
|
optOut.optOutManaged()
|
|
|
|
else:
|
|
|
|
optOut.optOutManaged()
|
|
|
|
assert optOut.value == 'opt_out_managed'
|
|
|
|
# Reset original value
|
|
|
|
optOut._updateOptOut(optOutValue)
|
|
|
|
|
2020-08-30 06:20:30 +00:00
|
|
|
with pytest.raises(NotFound):
|
2021-06-07 01:39:45 +00:00
|
|
|
onlineMediaSources[0]._updateOptOut('unknown')
|
2020-08-30 06:20:30 +00:00
|
|
|
|
|
|
|
|
2021-11-21 00:16:01 +00:00
|
|
|
def test_myplex_inviteFriend(account, plex, mocker):
|
2020-04-29 21:23:22 +00:00
|
|
|
inv_user = "hellowlol"
|
|
|
|
vid_filter = {"contentRating": ["G"], "label": ["foo"]}
|
2017-10-26 23:09:17 +00:00
|
|
|
secs = plex.library.sections()
|
|
|
|
|
|
|
|
ids = account._getSectionIds(plex.machineIdentifier, secs)
|
2020-12-30 22:58:01 +00:00
|
|
|
mocker.patch.object(account, "_getSectionIds", return_value=ids)
|
|
|
|
with utils.callable_http_patch():
|
|
|
|
account.inviteFriend(
|
|
|
|
inv_user,
|
|
|
|
plex,
|
|
|
|
secs,
|
|
|
|
allowSync=True,
|
|
|
|
allowCameraUpload=True,
|
|
|
|
allowChannels=False,
|
|
|
|
filterMovies=vid_filter,
|
|
|
|
filterTelevision=vid_filter,
|
|
|
|
filterMusic={"label": ["foo"]},
|
|
|
|
)
|
2017-10-26 23:09:17 +00:00
|
|
|
|
|
|
|
assert inv_user not in [u.title for u in account.users()]
|
|
|
|
|
2021-11-21 00:16:01 +00:00
|
|
|
|
|
|
|
def test_myplex_acceptInvite(account, requests_mock):
|
|
|
|
url = MyPlexInvite.REQUESTS
|
|
|
|
requests_mock.get(url, text=MYPLEX_INVITE)
|
|
|
|
invite = account.pendingInvite('testuser', includeSent=False)
|
|
|
|
with utils.callable_http_patch():
|
|
|
|
account.acceptInvite(invite)
|
|
|
|
|
|
|
|
|
|
|
|
def test_myplex_cancelInvite(account, requests_mock):
|
|
|
|
url = MyPlexInvite.REQUESTED
|
|
|
|
requests_mock.get(url, text=MYPLEX_INVITE)
|
|
|
|
invite = account.pendingInvite('testuser', includeReceived=False)
|
|
|
|
with utils.callable_http_patch():
|
|
|
|
account.cancelInvite(invite)
|
2017-10-28 20:58:47 +00:00
|
|
|
|
|
|
|
|
2018-09-14 18:03:23 +00:00
|
|
|
def test_myplex_updateFriend(account, plex, mocker, shared_username):
|
2020-04-29 21:23:22 +00:00
|
|
|
vid_filter = {"contentRating": ["G"], "label": ["foo"]}
|
2017-10-28 20:58:47 +00:00
|
|
|
secs = plex.library.sections()
|
2018-09-14 18:03:23 +00:00
|
|
|
user = account.user(shared_username)
|
2017-10-28 20:58:47 +00:00
|
|
|
|
|
|
|
ids = account._getSectionIds(plex.machineIdentifier, secs)
|
2020-12-30 22:58:01 +00:00
|
|
|
mocker.patch.object(account, "_getSectionIds", return_value=ids)
|
|
|
|
mocker.patch.object(account, "user", return_value=user)
|
|
|
|
with utils.callable_http_patch():
|
|
|
|
account.updateFriend(
|
|
|
|
shared_username,
|
|
|
|
plex,
|
|
|
|
secs,
|
|
|
|
allowSync=True,
|
|
|
|
removeSections=True,
|
|
|
|
allowCameraUpload=True,
|
|
|
|
allowChannels=False,
|
|
|
|
filterMovies=vid_filter,
|
|
|
|
filterTelevision=vid_filter,
|
|
|
|
filterMusic={"label": ["foo"]},
|
|
|
|
)
|
2018-09-14 18:03:23 +00:00
|
|
|
|
2021-11-21 00:16:01 +00:00
|
|
|
with utils.callable_http_patch():
|
|
|
|
account.removeFriend(shared_username)
|
|
|
|
|
2017-10-28 20:58:47 +00:00
|
|
|
|
2019-09-15 01:03:18 +00:00
|
|
|
def test_myplex_createExistingUser(account, plex, shared_username):
|
|
|
|
user = account.user(shared_username)
|
2022-08-28 05:56:01 +00:00
|
|
|
url = f"https://plex.tv/api/invites/requested/{user.id}?friend=0&server=0&home=1"
|
2019-09-15 01:03:18 +00:00
|
|
|
|
|
|
|
account.createExistingUser(user, plex)
|
|
|
|
assert shared_username in [u.username for u in account.users() if u.home is True]
|
|
|
|
# Remove Home invite
|
|
|
|
account.query(url, account._session.delete)
|
|
|
|
# Confirm user was removed from home and has returned to friend
|
2020-04-29 21:23:22 +00:00
|
|
|
assert shared_username not in [
|
|
|
|
u.username for u in plex.myPlexAccount().users() if u.home is True
|
|
|
|
]
|
|
|
|
assert shared_username in [
|
|
|
|
u.username for u in plex.myPlexAccount().users() if u.home is False
|
|
|
|
]
|
2019-09-15 01:03:18 +00:00
|
|
|
|
|
|
|
|
2019-10-03 01:08:27 +00:00
|
|
|
@pytest.mark.skip(reason="broken test?")
|
2019-09-15 01:03:18 +00:00
|
|
|
def test_myplex_createHomeUser_remove(account, plex):
|
2020-04-29 21:23:22 +00:00
|
|
|
homeuser = "New Home User"
|
2019-09-15 01:03:18 +00:00
|
|
|
account.createHomeUser(homeuser, plex)
|
|
|
|
assert homeuser in [u.title for u in plex.myPlexAccount().users() if u.home is True]
|
|
|
|
account.removeHomeUser(homeuser)
|
2020-04-29 21:23:22 +00:00
|
|
|
assert homeuser not in [
|
|
|
|
u.title for u in plex.myPlexAccount().users() if u.home is True
|
|
|
|
]
|
2019-09-15 01:03:18 +00:00
|
|
|
|
|
|
|
|
2018-09-14 18:03:23 +00:00
|
|
|
def test_myplex_plexpass_attributes(account_plexpass):
|
|
|
|
assert account_plexpass.subscriptionActive
|
2020-04-29 21:23:22 +00:00
|
|
|
assert account_plexpass.subscriptionStatus == "Active"
|
2018-09-14 18:03:23 +00:00
|
|
|
assert account_plexpass.subscriptionPlan
|
2020-04-29 21:23:22 +00:00
|
|
|
assert "sync" in account_plexpass.subscriptionFeatures
|
|
|
|
assert "premium_music_metadata" in account_plexpass.subscriptionFeatures
|
|
|
|
assert "plexpass" in account_plexpass.roles
|
2020-12-16 04:41:04 +00:00
|
|
|
assert utils.ENTITLEMENTS <= set(account_plexpass.entitlements)
|
2018-09-14 18:52:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_myplex_claimToken(account):
|
2020-04-29 21:23:22 +00:00
|
|
|
assert account.claimToken().startswith("claim-")
|
2022-05-18 18:04:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_myplex_watchlist(account, movie, show, artist):
|
2022-08-26 17:31:27 +00:00
|
|
|
# Ensure watchlist is cleared before tests
|
|
|
|
for item in account.watchlist():
|
|
|
|
account.removeFromWatchlist(item)
|
2022-05-18 18:04:18 +00:00
|
|
|
assert not account.watchlist()
|
|
|
|
|
|
|
|
# Add to watchlist from account
|
|
|
|
account.addToWatchlist(movie)
|
|
|
|
assert account.onWatchlist(movie)
|
|
|
|
|
|
|
|
# Add to watchlist from object
|
|
|
|
show.addToWatchlist(account)
|
|
|
|
assert show.onWatchlist(account)
|
|
|
|
|
|
|
|
# Remove from watchlist from account
|
|
|
|
account.removeFromWatchlist(show)
|
|
|
|
assert not account.onWatchlist(show)
|
|
|
|
|
|
|
|
# Remove from watchlist from object
|
|
|
|
movie.removeFromWatchlist(account)
|
|
|
|
assert not movie.onWatchlist(account)
|
|
|
|
|
|
|
|
# Add multiple items to watchlist
|
|
|
|
account.addToWatchlist([movie, show])
|
|
|
|
assert movie.onWatchlist(account) and show.onWatchlist(account)
|
|
|
|
|
|
|
|
# Filter and sort watchlist
|
|
|
|
watchlist = account.watchlist(filter='released', sort='titleSort', libtype='movie')
|
|
|
|
guids = [i.guid for i in watchlist]
|
|
|
|
assert movie.guid in guids and show.guid not in guids
|
|
|
|
|
|
|
|
# Test adding existing item to watchlist
|
|
|
|
with pytest.raises(BadRequest):
|
|
|
|
account.addToWatchlist(movie)
|
|
|
|
|
2022-08-27 07:20:11 +00:00
|
|
|
# Test retrieving maxresults from watchlist
|
|
|
|
watchlist = account.watchlist(maxresults=1)
|
|
|
|
assert len(watchlist) == 1
|
|
|
|
|
2022-05-18 18:04:18 +00:00
|
|
|
# Remove multiple items from watchlist
|
|
|
|
account.removeFromWatchlist([movie, show])
|
|
|
|
assert not movie.onWatchlist(account) and not show.onWatchlist(account)
|
|
|
|
|
|
|
|
# Test removing non-existing item from watchlist
|
|
|
|
with pytest.raises(BadRequest):
|
|
|
|
account.removeFromWatchlist(movie)
|
|
|
|
|
|
|
|
# Test adding invalid item to watchlist
|
2024-04-19 18:18:25 +00:00
|
|
|
with pytest.raises(BadRequest):
|
2022-05-18 18:04:18 +00:00
|
|
|
account.addToWatchlist(artist)
|
2022-07-21 02:37:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_myplex_searchDiscover(account, movie, show):
|
|
|
|
guids = lambda x: [r.guid for r in x]
|
|
|
|
|
|
|
|
results = account.searchDiscover(movie.title)
|
|
|
|
assert movie.guid in guids(results)
|
|
|
|
results = account.searchDiscover(movie.title, libtype="show")
|
|
|
|
assert movie.guid not in guids(results)
|
|
|
|
|
|
|
|
results = account.searchDiscover(show.title)
|
|
|
|
assert show.guid in [r.guid for r in results]
|
|
|
|
results = account.searchDiscover(show.title, libtype="movie")
|
|
|
|
assert show.guid not in guids(results)
|
2022-08-26 19:00:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.authenticated
|
|
|
|
def test_myplex_viewStateSync(account):
|
|
|
|
account.enableViewStateSync()
|
|
|
|
assert account.viewStateSync is True
|
|
|
|
account.disableViewStateSync()
|
|
|
|
assert account.viewStateSync is False
|
2022-11-08 23:57:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.authenticated
|
|
|
|
def test_myplex_pin(account, plex):
|
|
|
|
assert account.pin is None
|
|
|
|
|
|
|
|
account.setPin("0000")
|
|
|
|
|
|
|
|
with pytest.raises(Unauthorized):
|
|
|
|
account.setPin("1111")
|
|
|
|
account.setPin("1111", currentPin="0000")
|
|
|
|
|
|
|
|
with pytest.raises(Unauthorized):
|
2022-11-09 00:29:15 +00:00
|
|
|
account.removePin("0000")
|
2022-11-08 23:57:54 +00:00
|
|
|
account.removePin("1111")
|
|
|
|
|
|
|
|
homeuser = "Test PIN User"
|
|
|
|
try:
|
|
|
|
account.createHomeUser(homeuser, plex)
|
|
|
|
account.setManagedUserPin(homeuser, "0000")
|
|
|
|
account.removeManagedUserPin(homeuser)
|
|
|
|
finally:
|
|
|
|
account.removeHomeUser(homeuser)
|
2023-08-27 19:34:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_myplex_geoip(account):
|
|
|
|
assert account.geoip(account.publicIP())
|
2023-11-03 04:24:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_myplex_ping(account):
|
|
|
|
assert account.ping()
|