mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 02:48:38 -04:00
627 lines
30 KiB
Python
627 lines
30 KiB
Python
import asyncio
|
|
import json
|
|
import pathlib
|
|
import shutil
|
|
import tempfile
|
|
from datetime import timedelta
|
|
from http import HTTPStatus
|
|
|
|
import mock
|
|
import pytest
|
|
|
|
from modules.videos.common import get_videos_directory
|
|
from modules.videos.downloader import ChannelDownloader
|
|
from modules.videos.lib import save_channels_config, get_channels_config
|
|
from modules.videos.models import Channel, Video
|
|
from wrolpi.common import get_relative_to_media_directory, walk
|
|
from wrolpi.dates import now
|
|
from wrolpi.db import get_db_session
|
|
from wrolpi.downloader import download_manager, Download, DownloadResult
|
|
from wrolpi.files.models import Directory
|
|
from wrolpi.test.common import assert_dict_contains
|
|
|
|
|
|
def test_get_channels(test_directory, channel_factory, test_client):
|
|
channel_factory()
|
|
channel_factory()
|
|
channel_factory()
|
|
request, response = test_client.get('/api/videos/channels')
|
|
assert response.status_code == HTTPStatus.OK
|
|
assert len(response.json['channels']) == 3
|
|
|
|
|
|
def test_get_video(test_client, test_session, simple_channel, video_factory):
|
|
"""Test that you get can information about a video. Test that video file can be gotten."""
|
|
now_ = now()
|
|
video1 = video_factory(channel_id=simple_channel.id, title='vid1')
|
|
video1.file_group.published_datetime = now_
|
|
video2 = video_factory(channel_id=simple_channel.id, title='vid2')
|
|
video2.file_group.published_datetime = now_ + timedelta(seconds=1)
|
|
test_session.commit()
|
|
|
|
# Test that a 404 is returned when no video exists
|
|
_, response = test_client.get('/api/videos/video/10')
|
|
assert response.status_code == HTTPStatus.NOT_FOUND, response.json
|
|
assert response.json == {'code': 'UNKNOWN_VIDEO',
|
|
'error': 'Cannot find Video with id 10',
|
|
'message': 'The video could not be found.'}
|
|
|
|
# Get the video info we inserted
|
|
_, response = test_client.get('/api/videos/video/1')
|
|
assert response.status_code == HTTPStatus.OK, response.json
|
|
assert_dict_contains(response.json['file_group'], {'title': 'vid1'})
|
|
|
|
# The next video is included.
|
|
assert response.json['prev'] is None
|
|
assert_dict_contains(response.json['next'], {'title': 'vid2'})
|
|
|
|
|
|
def test_channel_no_download_frequency(test_client, test_session, test_directory, simple_channel):
|
|
"""A channel does not require a download frequency."""
|
|
# No downloads are scheduled.
|
|
assert len(download_manager.get_downloads(test_session)) == 0
|
|
|
|
# Get the Channel
|
|
request, response = test_client.get('/api/videos/channels/1')
|
|
assert response.status_code == HTTPStatus.OK
|
|
channel = response.json['channel']
|
|
assert not channel['downloads']
|
|
|
|
# Update the Channel with a frequency.
|
|
new_download = {'urls': [simple_channel.url], 'frequency': 10, 'downloader': ChannelDownloader.name}
|
|
request, response = test_client.post('/api/download', json=new_download)
|
|
assert response.status_code == HTTPStatus.NO_CONTENT, response.json
|
|
|
|
# Download is scheduled. Download is related to Channel.
|
|
assert len(download_manager.get_downloads(test_session)) == 1
|
|
request, response = test_client.get('/api/videos/channels/1')
|
|
channel = response.json['channel']
|
|
assert channel['downloads']
|
|
downloads = channel['downloads']
|
|
assert len(downloads) == 1 and downloads[0]['url'] == simple_channel.url
|
|
assert test_session.query(Download).one()
|
|
download_id = channel['downloads'][0]['id']
|
|
|
|
# Deleting Download does not delete Channel.
|
|
request, response = test_client.delete(f'/api/download/{download_id}')
|
|
assert response.status_code == HTTPStatus.NO_CONTENT, response.json
|
|
assert not download_manager.get_downloads(test_session)
|
|
assert not test_session.query(Download).all()
|
|
assert test_session.query(Channel).count() == 1
|
|
|
|
|
|
def test_video_file_name(test_session, simple_video, test_client):
|
|
"""If a Video has no title, the front-end can use the file name as the title."""
|
|
_, resp = test_client.get(f'/api/videos/video/{simple_video.id}')
|
|
assert resp.status_code == HTTPStatus.OK
|
|
assert resp.json['file_group']['video']['video_path'] == 'simple_video.mp4'
|
|
assert resp.json['file_group']['video'].get('stem') == 'simple_video'
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_channel_conflicts(async_client, test_session, test_directory):
|
|
channel_directory = tempfile.TemporaryDirectory(dir=test_directory).name
|
|
pathlib.Path(channel_directory).mkdir()
|
|
new_channel = dict(
|
|
directory=channel_directory,
|
|
name='Example Channel 1',
|
|
url='https://example.com/channel1',
|
|
)
|
|
|
|
async def _post_channel(channel):
|
|
return await async_client.post('/api/videos/channels', content=json.dumps(channel))
|
|
|
|
# Create it
|
|
request, response = await _post_channel(new_channel)
|
|
assert response.status_code == HTTPStatus.CREATED
|
|
|
|
# Name is an exact match
|
|
channel_directory2 = tempfile.TemporaryDirectory(dir=test_directory).name
|
|
pathlib.Path(channel_directory2).mkdir()
|
|
new_channel = dict(
|
|
directory=channel_directory2,
|
|
name='Example Channel 1',
|
|
)
|
|
request, response = await _post_channel(new_channel)
|
|
assert response.status_code == HTTPStatus.BAD_REQUEST
|
|
assert response.json == {'cause': {'code': 'CHANNEL_NAME_CONFLICT',
|
|
'error': 'The channel name is already taken.',
|
|
'message': 'The channel name is already taken.'},
|
|
'code': 'VALIDATION_ERROR',
|
|
'error': 'Could not validate the contents of the request',
|
|
'message': 'Could not validate the contents of the request'}
|
|
|
|
# Directory was already used
|
|
new_channel = dict(
|
|
directory=channel_directory,
|
|
name='name is fine',
|
|
)
|
|
request, response = await _post_channel(new_channel)
|
|
assert response.status_code == HTTPStatus.BAD_REQUEST
|
|
assert response.json == {'cause': {'code': 'CHANNEL_DIRECTORY_CONFLICT',
|
|
'error': 'The directory is already used by another channel.',
|
|
'message': 'The directory is already used by another channel.'},
|
|
'code': 'VALIDATION_ERROR',
|
|
'error': 'Could not validate the contents of the request',
|
|
'message': 'Could not validate the contents of the request'}
|
|
|
|
# URL is already used
|
|
channel_directory3 = tempfile.TemporaryDirectory(dir=test_directory).name
|
|
pathlib.Path(channel_directory3).mkdir()
|
|
new_channel = dict(
|
|
directory=channel_directory3,
|
|
name='name is fine',
|
|
url='https://example.com/channel1',
|
|
)
|
|
request, response = await _post_channel(new_channel)
|
|
assert response.status_code == HTTPStatus.BAD_REQUEST
|
|
assert response.json == {'cause': {'code': 'CHANNEL_URL_CONFLICT',
|
|
'error': 'The URL is already used by another channel.',
|
|
'message': 'The URL is already used by another channel.'},
|
|
'code': 'VALIDATION_ERROR',
|
|
'error': 'Could not validate the contents of the request',
|
|
'message': 'Could not validate the contents of the request'}
|
|
|
|
|
|
def test_channel_empty_url_doesnt_conflict(test_client, test_session, test_directory):
|
|
"""Two channels with empty URLs shouldn't conflict"""
|
|
channel_directory = tempfile.TemporaryDirectory(dir=test_directory).name
|
|
pathlib.Path(channel_directory).mkdir()
|
|
|
|
new_channel = {
|
|
'name': 'Fooz',
|
|
'directory': channel_directory,
|
|
}
|
|
request, response = test_client.post('/api/videos/channels', content=json.dumps(new_channel))
|
|
assert response.status_code == HTTPStatus.CREATED, response.json
|
|
location = response.headers['Location']
|
|
|
|
channel_directory2 = tempfile.TemporaryDirectory(dir=test_directory).name
|
|
pathlib.Path(channel_directory2).mkdir()
|
|
new_channel = {
|
|
'name': 'Barz',
|
|
'directory': channel_directory2,
|
|
}
|
|
request, response = test_client.post('/api/videos/channels', content=json.dumps(new_channel))
|
|
assert response.status_code == HTTPStatus.CREATED, response.json
|
|
assert location != response.headers['Location']
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_channel_download_requires_refresh(
|
|
async_client, test_session, mock_video_extract_info, download_channel, video_download_manager,
|
|
video_factory, events_history):
|
|
"""A Channel cannot be downloaded until it has been refreshed.
|
|
|
|
Videos already downloaded are not downloaded again."""
|
|
vid = video_factory(channel_id=download_channel.id, with_video_file=True, with_poster_ext='jpg')
|
|
vid.file_group.url = 'https://example.com/1'
|
|
d = download_channel.downloads[0]
|
|
d.next_download = None
|
|
test_session.commit()
|
|
assert not d.next_download
|
|
|
|
def assert_refreshed(expected: bool):
|
|
channel: Channel = test_session.query(Channel).one()
|
|
assert channel.refreshed == expected
|
|
assert bool(channel.info_json) == expected
|
|
|
|
await async_client.sanic_app.dispatch('wrolpi.download.')
|
|
|
|
assert_refreshed(False)
|
|
test_session.commit()
|
|
|
|
downloaded_urls = []
|
|
|
|
async def do_download(_, download):
|
|
# Track all downloaded URLs.
|
|
downloaded_urls.append(download.url)
|
|
return DownloadResult(success=True)
|
|
|
|
with mock.patch('modules.videos.downloader.VideoDownloader.do_download', do_download), \
|
|
mock.patch('wrolpi.files.lib.apply_indexers'): # TODO why does apply_indexers break this test?
|
|
entries = [
|
|
dict(id=vid.source_id, view_count=0, webpage_url='https://example.com/1'), # Already downloaded.
|
|
dict(id='not downloaded', view_count=0, webpage_url='https://example.com/2'),
|
|
]
|
|
mock_video_extract_info.return_value = dict(entries=entries, url=download_channel.url, uploader='some uploader',
|
|
channel_id='the id', id='the id')
|
|
await video_download_manager.do_downloads()
|
|
await video_download_manager.wait_for_all_downloads()
|
|
# Give time for background tasks to finish. :(
|
|
await asyncio.sleep(1)
|
|
|
|
# Channel was refreshed before downloading videos.
|
|
assert_refreshed(True)
|
|
assert d.next_download
|
|
# Only the missing video was downloaded.
|
|
assert downloaded_urls == ['https://example.com/2']
|
|
|
|
# Should not send refresh events because downloads are automated.
|
|
assert list(events_history) == []
|
|
|
|
|
|
def test_channel_post_directory(test_session, test_client, test_directory):
|
|
"""A Channel can be created with or without an existing directory."""
|
|
# Channel can be created with a directory which is not on disk.
|
|
data = dict(name='foo', directory='foo')
|
|
request, response = test_client.post('/api/videos/channels', content=json.dumps(data))
|
|
assert response.status_code == HTTPStatus.CREATED
|
|
directory = test_session.query(Channel).filter_by(id=1).one().directory
|
|
assert (test_directory / 'foo') == directory
|
|
assert directory.is_dir()
|
|
assert directory.is_absolute()
|
|
|
|
|
|
def test_channel_by_id(test_session, test_client, simple_channel, simple_video):
|
|
request, response = test_client.get(f'/api/videos/channels/{simple_channel.id}')
|
|
assert response.status_code == HTTPStatus.OK
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_channel_crud(test_session, async_client, test_directory, test_download_manager):
|
|
channel_directory = test_directory / 'channel directory'
|
|
channel_directory.mkdir()
|
|
|
|
new_channel = dict(
|
|
directory=str(channel_directory),
|
|
name=' Example Channel 1 ',
|
|
url='https://example.com/channel1',
|
|
)
|
|
|
|
# Channel doesn't exist
|
|
request, response = await async_client.get('/api/videos/channels/examplechannel1')
|
|
assert response.status_code == HTTPStatus.NOT_FOUND, f'Channel exists: {response.json}'
|
|
|
|
# Create it
|
|
request, response = await async_client.post('/api/videos/channels', content=json.dumps(new_channel))
|
|
assert response.status_code == HTTPStatus.CREATED, response.content
|
|
location = response.headers['Location']
|
|
|
|
request, response = await async_client.get(location)
|
|
assert response.status_code == HTTPStatus.OK, response.json
|
|
created = response.json['channel']
|
|
assert created
|
|
assert created['id']
|
|
# No frequency was provided. No download exists.
|
|
assert test_session.query(Download).count() == 0
|
|
|
|
# Channel name leading/trailing whitespace should be stripped
|
|
assert created['name'] == 'Example Channel 1'
|
|
|
|
# Channel directory should be relative to the media directory
|
|
assert not pathlib.Path(created['directory']).is_absolute(), \
|
|
f'Channel directory is absolute: {created["directory"]}'
|
|
|
|
# Can't create it again
|
|
request, response = await async_client.post('/api/videos/channels', content=json.dumps(new_channel))
|
|
assert response.status_code == HTTPStatus.BAD_REQUEST
|
|
|
|
async def put_and_fetch(new_channel_):
|
|
request, response = await async_client.put(location, content=json.dumps(new_channel_))
|
|
assert response.status_code == HTTPStatus.NO_CONTENT, response.status_code
|
|
request, response = await async_client.get(location)
|
|
assert response.status_code == HTTPStatus.OK
|
|
channel = response.json['channel']
|
|
return channel
|
|
|
|
# Update it
|
|
new_channel['name'] = 'Example Channel 2'
|
|
new_channel['directory'] = str(new_channel['directory']) # noqa
|
|
channel = await put_and_fetch(new_channel)
|
|
assert channel['id'] == 1
|
|
assert channel['name'] == 'Example Channel 2'
|
|
assert channel['directory'] == channel_directory.name
|
|
assert channel['url'] == 'https://example.com/channel1'
|
|
|
|
# Can't update channel that doesn't exist
|
|
request, response = await async_client.put('/api/videos/channels/doesnt_exist',
|
|
content=json.dumps(new_channel))
|
|
assert response.status_code == HTTPStatus.NOT_FOUND
|
|
|
|
# Delete the new channel
|
|
request, response = await async_client.delete(location)
|
|
assert response.status_code == HTTPStatus.NO_CONTENT
|
|
assert test_session.query(Download).count() == 0
|
|
|
|
# Cant delete it again
|
|
request, response = await async_client.delete(location)
|
|
assert response.status_code == HTTPStatus.NOT_FOUND
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_change_channel_url(async_client, test_session, test_download_manager, download_channel,
|
|
test_download_manager_config):
|
|
# Change the Channel's URL.
|
|
download_channel.update({'url': 'https://example.com/new-url'})
|
|
test_session.commit()
|
|
|
|
channel = test_session.query(Channel).one()
|
|
assert channel.url == 'https://example.com/new-url', "Channel's URL was not changed."
|
|
|
|
|
|
def test_search_videos_channel(test_client, test_session, video_factory):
|
|
with get_db_session(commit=True) as session:
|
|
channel1 = Channel(name='Foo')
|
|
channel2 = Channel(name='Bar')
|
|
session.add(channel1)
|
|
session.add(channel2)
|
|
session.flush()
|
|
session.refresh(channel1)
|
|
session.refresh(channel2)
|
|
|
|
# Channels don't have videos yet
|
|
d = dict(channel_id=channel1.id)
|
|
request, response = test_client.post(f'/api/videos/search', content=json.dumps(d))
|
|
assert response.status_code == HTTPStatus.OK
|
|
assert len(response.json['file_groups']) == 0
|
|
|
|
with get_db_session(commit=True) as session:
|
|
vid1 = video_factory(title='vid1', channel_id=channel2.id)
|
|
vid2 = video_factory(title='vid2', channel_id=channel1.id)
|
|
session.add(vid1)
|
|
session.add(vid2)
|
|
|
|
# Videos are gotten by their respective channels
|
|
request, response = test_client.post(f'/api/videos/search', content=json.dumps(d))
|
|
assert response.status_code == HTTPStatus.OK
|
|
assert len(response.json['file_groups']) == 1
|
|
assert response.json['totals']['file_groups'] == 1
|
|
assert_dict_contains(response.json['file_groups'][0],
|
|
dict(primary_path='vid2.mp4', video=dict(channel_id=channel1.id)))
|
|
|
|
d = dict(channel_id=channel2.id)
|
|
request, response = test_client.post(f'/api/videos/search', content=json.dumps(d))
|
|
assert response.status_code == HTTPStatus.OK
|
|
assert len(response.json['file_groups']) == 1
|
|
assert_dict_contains(response.json['file_groups'][0],
|
|
dict(primary_path='vid1.mp4', video=dict(channel_id=channel2.id)))
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_channel_videos_pagination(test_session, simple_channel, video_factory, assert_video_search):
|
|
for i in range(50):
|
|
video_factory(channel_id=simple_channel.id)
|
|
|
|
channel2 = Channel(name='Bar')
|
|
test_session.add(channel2)
|
|
test_session.flush()
|
|
test_session.refresh(channel2)
|
|
video_factory(channel_id=channel2.id)
|
|
video_factory(title='vid2', channel_id=channel2.id, with_video_file=True)
|
|
test_session.commit()
|
|
|
|
# Get first, second, third, and empty pages of videos.
|
|
tests = [
|
|
# (offset, video_count)
|
|
(0, 20),
|
|
(20, 20),
|
|
(40, 10),
|
|
(50, 0),
|
|
]
|
|
last_ids = []
|
|
for offset, video_count in tests:
|
|
_, response = await assert_video_search(channel_id=simple_channel.id, order_by='published_datetime',
|
|
offset=offset,
|
|
limit=20)
|
|
assert len(response.json['file_groups']) == video_count, 'Returned videos does not match'
|
|
current_ids = [i['id'] for i in response.json['file_groups']]
|
|
assert current_ids != last_ids, f'IDs are unchanged current_ids={current_ids}'
|
|
last_ids = current_ids
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_channel_download_api(async_client, test_session, simple_channel, tag_factory):
|
|
tag = await tag_factory()
|
|
|
|
# Create Download.
|
|
body = {'url': 'https://example.com/1', 'frequency': 42, 'settings': {'title_include': ''}}
|
|
request, response = await async_client.post(
|
|
f'/api/videos/channels/{simple_channel.id}/download', json=body)
|
|
assert response.status_code == HTTPStatus.NO_CONTENT, response.content
|
|
download: Download = test_session.query(Download).one()
|
|
assert download.url == 'https://example.com/1' and not download.settings.get('tag_names')
|
|
assert download.settings.get('title_include') is None
|
|
|
|
# Create another Download. Previous Downloads is untouched.
|
|
body = {'url': 'https://example.com/2', 'frequency': 55, 'settings': {'tag_names': [tag.name]}}
|
|
request, response = await async_client.post(
|
|
f'/api/videos/channels/{simple_channel.id}/download', json=body)
|
|
assert response.status_code == HTTPStatus.NO_CONTENT, response.content
|
|
download1, download2 = test_session.query(Download).order_by(Download.url).all()
|
|
assert download1.url == 'https://example.com/1' and not download1.settings.get('tag_names')
|
|
assert download2.url == 'https://example.com/2' and download2.settings.get('tag_names') == [tag.name, ]
|
|
test_session.flush(simple_channel)
|
|
assert len(simple_channel.downloads) == 2
|
|
|
|
# Change frequency of first Download.
|
|
body = {'url': 'https://example.com/1', 'frequency': 123, 'settings': {'title_include': 'foo,bar'}}
|
|
request, response = await async_client.put(
|
|
f'/api/videos/channels/{simple_channel.id}/download/{download1.id}', json=body)
|
|
assert response.status_code == HTTPStatus.NO_CONTENT, response.content
|
|
download1, download2 = test_session.query(Download).order_by(Download.url).all()
|
|
assert download1.url == 'https://example.com/1'
|
|
assert download1.settings == {'title_include': 'foo,bar'}
|
|
assert download2.url == 'https://example.com/2' and download2.settings.get('tag_names') == [tag.name, ]
|
|
test_session.flush(simple_channel)
|
|
assert len(simple_channel.downloads) == 2
|
|
|
|
# Deleting Download does not delete Channel.
|
|
request, response = await async_client.delete(f'/api/download/{download1.id}')
|
|
assert response.status_code == HTTPStatus.NO_CONTENT
|
|
assert test_session.query(Download).count() == 1
|
|
assert test_session.query(Channel).count() == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_tag_channel(async_client, test_session, test_directory, channel_factory, tag_factory, video_factory,
|
|
test_channels_config, test_download_manager, test_downloader):
|
|
"""A single Tag can be applied to a Channel."""
|
|
# Create channel directory in the usual videos directory.
|
|
videos_directory = get_videos_directory()
|
|
channel = channel_factory(name='Channel Name', download_frequency=120)
|
|
channel_directory = channel.directory
|
|
tag = await tag_factory('Tag Name')
|
|
v1, v2 = video_factory(title='video1', channel_id=channel.id), video_factory(title='video2', channel_id=channel.id)
|
|
# Create recurring download which uses the Channel's directory.
|
|
download = download_manager.recurring_download('https://example.com/1', 60, test_downloader.name,
|
|
settings={
|
|
'destination': str(test_directory / 'videos/Channel Name')})
|
|
test_session.commit()
|
|
save_channels_config()
|
|
assert get_channels_config().channels[0]['directory'] == str(test_directory / 'videos/Channel Name')
|
|
# Channel download downloads into the Channel's directory.
|
|
assert channel.downloads[0].settings['destination'] == str(test_directory / 'videos/Channel Name')
|
|
# Make extra file in the Channel's directory, it should be moved.
|
|
(channel_directory / 'extra file.txt').write_text('extra file contents')
|
|
# Channel directory is in the Videos directory.
|
|
assert channel.directory == channel_directory
|
|
# Videos are in the Channel's Directory.
|
|
assert str(get_relative_to_media_directory(v1.video_path)) == 'videos/Channel Name/video1.mp4'
|
|
assert str(get_relative_to_media_directory(v2.video_path)) == 'videos/Channel Name/video2.mp4'
|
|
video_files = [i for i in walk(videos_directory) if i.is_file()]
|
|
assert len(video_files) == 3
|
|
# Channel's directory is indexed.
|
|
assert [i.path for i in test_session.query(Directory)] == [channel.directory, ]
|
|
|
|
body = dict(tag_name=tag.name, directory='videos/Tag Name/Channel Name')
|
|
request, response = await async_client.post(f'/api/videos/channels/{channel.id}/tag', json=body)
|
|
assert response.status_code == HTTPStatus.NO_CONTENT
|
|
|
|
channel = test_session.query(Channel).one()
|
|
assert channel.tag_name == tag.name
|
|
# Channel was moved to Tag's directory, old directory was removed
|
|
new_channel_directory = test_directory / 'videos/Tag Name/Channel Name'
|
|
assert channel.directory == new_channel_directory, 'Channel directory should have been changed.'
|
|
assert new_channel_directory.is_dir(), 'Channel should have been moved.'
|
|
assert next(new_channel_directory.iterdir(), None), 'Channel videos should have been moved.'
|
|
assert not channel_directory.exists(), 'Old Channel directory should have been deleted.'
|
|
# Channel download goes into the Channel's directory.
|
|
d1, d2 = test_session.query(Download).all()
|
|
assert d1.settings['destination'] == str(test_directory / 'videos/Tag Name/Channel Name'), \
|
|
f'{d1} was not moved'
|
|
assert d2.settings['destination'] == str(test_directory / 'videos/Tag Name/Channel Name'), \
|
|
f'{d2} was not moved'
|
|
# Directory record was replaced.
|
|
assert [i.path for i in test_session.query(Directory)] == [channel.directory, ]
|
|
|
|
# Videos were moved
|
|
v1, v2 = test_session.query(Video).order_by(Video.id).all()
|
|
assert str(get_relative_to_media_directory(v1.video_path)) == 'videos/Tag Name/Channel Name/video1.mp4'
|
|
assert str(get_relative_to_media_directory(v2.video_path)) == 'videos/Tag Name/Channel Name/video2.mp4'
|
|
# Extra file was also moved.
|
|
assert (new_channel_directory / 'extra file.txt').read_text() == 'extra file contents'
|
|
# No new files were created.
|
|
assert len([i for i in walk(videos_directory) if i.is_file()]) == 3
|
|
|
|
assert get_channels_config().channels[0]['directory'] == str(test_directory / 'videos/Tag Name/Channel Name')
|
|
|
|
# Remove tag, Channel/Videos should be moved back.
|
|
body = dict(tag_name=None, directory='videos/Channel Name')
|
|
request, response = await async_client.post(f'/api/videos/channels/{channel.id}/tag', json=body)
|
|
assert response.status_code == HTTPStatus.NO_CONTENT, response.content.decode()
|
|
channel = test_session.query(Channel).one()
|
|
assert channel.tag_name is None
|
|
assert str(channel.directory) == str(test_directory / 'videos/Channel Name')
|
|
v1, v2 = test_session.query(Video).order_by(Video.id).all()
|
|
assert str(get_relative_to_media_directory(v1.video_path)) == 'videos/Channel Name/video1.mp4'
|
|
assert str(get_relative_to_media_directory(v2.video_path)) == 'videos/Channel Name/video2.mp4'
|
|
assert not (test_directory / 'videos/Tag Name/Channel Name').exists()
|
|
# Downloads are moved back.
|
|
d1, d2 = test_session.query(Download).all()
|
|
assert d1.settings['destination'] == str(test_directory / 'videos/Channel Name'), f'{d1} was not moved'
|
|
assert d2.settings['destination'] == str(test_directory / 'videos/Channel Name'), f'{d2} was not moved'
|
|
assert [i.path for i in test_session.query(Directory)] == [channel.directory, ]
|
|
|
|
# Channel can be Tagged, without moving directories.
|
|
body = dict(tag_name=tag.name)
|
|
request, response = await async_client.post(f'/api/videos/channels/{channel.id}/tag', json=body)
|
|
assert response.status_code == HTTPStatus.NO_CONTENT, response.content.decode()
|
|
channel = test_session.query(Channel).one()
|
|
assert channel.tag_name == 'Tag Name'
|
|
assert str(channel.directory) == str(test_directory / 'videos/Channel Name')
|
|
assert str(get_relative_to_media_directory(v1.video_path)) == 'videos/Channel Name/video1.mp4'
|
|
assert str(get_relative_to_media_directory(v2.video_path)) == 'videos/Channel Name/video2.mp4'
|
|
# Downloads were not changed.
|
|
d1, d2 = test_session.query(Download).all()
|
|
assert d1.settings['destination'] == str(test_directory / 'videos/Channel Name'), f'{d1} was not moved'
|
|
assert d2.settings['destination'] == str(test_directory / 'videos/Channel Name'), f'{d2} was not moved'
|
|
assert [i.path for i in test_session.query(Directory)] == [channel.directory, ]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_move_channel(async_client, test_session, test_directory, channel_factory, tag_factory,
|
|
video_factory, test_channels_config, test_download_manager, test_downloader):
|
|
"""A Channel can be moved."""
|
|
channel = channel_factory(name='Channel Name')
|
|
vid1 = video_factory(channel_id=channel.id)
|
|
vid2 = video_factory(channel_id=channel.id)
|
|
test_session.commit()
|
|
|
|
assert str(channel.directory) == str(test_directory / 'videos/Channel Name')
|
|
assert str(vid1.video_path).startswith(str(test_directory / 'videos/Channel Name/'))
|
|
assert str(vid2.video_path).startswith(str(test_directory / 'videos/Channel Name/'))
|
|
|
|
body = dict(
|
|
name=channel.name,
|
|
directory=str(test_directory / 'videos/New Directory'),
|
|
)
|
|
request, response = await async_client.put(f'/api/videos/channels/{channel.id}', json=body)
|
|
assert response.status_code == HTTPStatus.NO_CONTENT, response.content.decode()
|
|
channel = Channel.find_by_id(channel.id)
|
|
assert str(channel.directory) == str(test_directory / 'videos/New Directory')
|
|
assert str(vid1.video_path).startswith(str(test_directory / 'videos/New Directory/'))
|
|
assert str(vid2.video_path).startswith(str(test_directory / 'videos/New Directory/'))
|
|
assert [i.path for i in test_session.query(Directory)] == [test_directory / 'videos/New Directory', ]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_move_channel_after_terminal_move(async_client, test_session, test_directory, channel_factory,
|
|
tag_factory, video_factory, test_channels_config, test_download_manager,
|
|
test_downloader):
|
|
"""A Channel can be moved in the UI after being moved in the Terminal."""
|
|
channel = channel_factory(name='Channel Name')
|
|
vid1 = video_factory(channel_id=channel.id)
|
|
test_session.commit()
|
|
|
|
# TODO this does not seem to work
|
|
assert str(channel.directory) == str(test_directory / 'videos/Channel Name')
|
|
assert str(vid1.video_path).startswith(str(test_directory / 'videos/Channel Name/'))
|
|
|
|
# mv 'videos/Channel Name' 'videos/New Directory'
|
|
shutil.move(channel.directory, test_directory / 'videos/New Directory')
|
|
|
|
body = dict(
|
|
name=channel.name,
|
|
directory=str(test_directory / 'videos/New Directory'),
|
|
)
|
|
request, response = await async_client.put(f'/api/videos/channels/{channel.id}', json=body)
|
|
assert response.status_code == HTTPStatus.NO_CONTENT, response.content.decode()
|
|
channel = Channel.find_by_id(channel.id)
|
|
vid1 = test_session.query(Video).one()
|
|
assert str(channel.directory) == str(test_directory / 'videos/New Directory')
|
|
assert str(vid1.video_path).startswith(str(test_directory / 'videos/New Directory/'))
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_search_tagged_channels(async_client, test_session, channel_factory, tag_factory,
|
|
video_factory):
|
|
"""Tagged Channels can be searched."""
|
|
tag = await tag_factory()
|
|
test_session.commit()
|
|
|
|
# Can search when empty.
|
|
body = dict(tag_names=[tag.name])
|
|
request, response = await async_client.post('/api/videos/channels/search', json=body)
|
|
assert response.status_code == HTTPStatus.OK
|
|
assert len(response.json['channels']) == 0, 'No Channels should be tagged'
|
|
|
|
channel1 = channel_factory(tag_name=tag.name)
|
|
channel2 = channel_factory()
|
|
test_session.commit()
|
|
|
|
body = dict(tag_names=[tag.name])
|
|
request, response = await async_client.post('/api/videos/channels/search', json=body)
|
|
assert response.status_code == HTTPStatus.OK
|
|
assert len(response.json['channels']) == 1, 'Only one Channel is tagged'
|
|
assert response.json['channels'][0]['id'] == channel1.id
|