2022-01-14 15:54:33 -07:00
|
|
|
import json
|
2021-12-21 20:57:35 -07:00
|
|
|
import pathlib
|
|
|
|
|
import shutil
|
2022-08-06 05:22:07 +00:00
|
|
|
from http import HTTPStatus
|
2022-05-10 22:44:59 -06:00
|
|
|
from itertools import zip_longest
|
|
|
|
|
from typing import List
|
2021-12-21 20:57:35 -07:00
|
|
|
from uuid import uuid4
|
|
|
|
|
|
2021-12-30 20:45:11 -07:00
|
|
|
import mock
|
2021-12-20 16:08:13 -07:00
|
|
|
import pytest
|
2022-01-14 15:54:33 -07:00
|
|
|
from PIL import Image
|
2021-12-20 16:08:13 -07:00
|
|
|
|
2021-12-30 20:45:11 -07:00
|
|
|
from modules.videos.downloader import VideoDownloader, ChannelDownloader
|
2022-02-16 23:05:43 -07:00
|
|
|
from modules.videos.lib import set_test_channels_config, set_test_downloader_config
|
2021-12-21 09:19:26 -07:00
|
|
|
from modules.videos.models import Channel, Video
|
2022-01-27 20:36:34 -07:00
|
|
|
from wrolpi.downloader import DownloadFrequency, DownloadManager, Download
|
2022-08-06 05:22:07 +00:00
|
|
|
from wrolpi.files.models import File
|
2021-12-21 20:57:35 -07:00
|
|
|
from wrolpi.vars import PROJECT_DIR
|
2021-12-20 16:08:13 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def simple_channel(test_session, test_directory) -> Channel:
|
2022-04-08 09:57:04 -06:00
|
|
|
"""Get a Channel with the minimum properties. This Channel has no download!"""
|
2021-12-20 16:08:13 -07:00
|
|
|
channel = Channel(
|
|
|
|
|
directory=test_directory,
|
2021-12-21 09:27:45 -07:00
|
|
|
name='Simple Channel',
|
2021-12-20 16:08:13 -07:00
|
|
|
url='https://example.com/channel1',
|
|
|
|
|
download_frequency=None, # noqa
|
|
|
|
|
)
|
|
|
|
|
test_session.add(channel)
|
|
|
|
|
test_session.commit()
|
|
|
|
|
return channel
|
|
|
|
|
|
|
|
|
|
|
2021-12-23 09:38:12 -07:00
|
|
|
@pytest.fixture
|
|
|
|
|
def channel_factory(test_session, test_directory):
|
2022-04-08 09:57:04 -06:00
|
|
|
"""Create a random Channel with a directory, but no frequency."""
|
2021-12-23 09:38:12 -07:00
|
|
|
|
2022-05-10 22:44:59 -06:00
|
|
|
def factory(source_id: str = None, download_frequency: DownloadFrequency = None, url: str = None, name: str = None):
|
2022-04-08 09:57:04 -06:00
|
|
|
name = name or str(uuid4())
|
2021-12-23 09:38:12 -07:00
|
|
|
directory = test_directory / name
|
|
|
|
|
directory.mkdir()
|
|
|
|
|
channel = Channel(
|
|
|
|
|
directory=directory, # noqa
|
|
|
|
|
name=name,
|
2022-04-08 09:57:04 -06:00
|
|
|
url=url or f'https://example.com/{name}',
|
|
|
|
|
download_frequency=download_frequency,
|
|
|
|
|
source_id=source_id,
|
2021-12-23 09:38:12 -07:00
|
|
|
)
|
|
|
|
|
test_session.add(channel)
|
|
|
|
|
test_session.commit()
|
|
|
|
|
return channel
|
|
|
|
|
|
2022-05-10 22:44:59 -06:00
|
|
|
return factory
|
2021-12-23 09:38:12 -07:00
|
|
|
|
|
|
|
|
|
2021-12-20 16:08:13 -07:00
|
|
|
@pytest.fixture
|
2022-01-27 20:36:34 -07:00
|
|
|
def download_channel(test_session, test_directory, video_download_manager) -> Channel:
|
2022-08-06 05:22:07 +00:00
|
|
|
"""Get a test Channel that has a download frequency."""
|
2021-12-20 16:08:13 -07:00
|
|
|
# Add a frequency to the test channel, then give it a download.
|
2021-12-21 09:27:45 -07:00
|
|
|
channel = Channel(
|
|
|
|
|
directory=test_directory,
|
|
|
|
|
name='Download Channel',
|
|
|
|
|
url='https://example.com/channel1',
|
|
|
|
|
download_frequency=DownloadFrequency.weekly,
|
|
|
|
|
)
|
2022-01-27 20:36:34 -07:00
|
|
|
download = Download(url=channel.url, downloader='video_channel', frequency=channel.download_frequency)
|
|
|
|
|
test_session.add_all([channel, download])
|
2021-12-21 09:27:45 -07:00
|
|
|
test_session.commit()
|
|
|
|
|
return channel
|
2021-12-21 09:19:26 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def simple_video(test_session, test_directory, simple_channel) -> Video:
|
2022-04-08 09:57:04 -06:00
|
|
|
"""A Video with an empty video file whose channel is the Simple Channel."""
|
2021-12-21 09:19:26 -07:00
|
|
|
video_path = test_directory / 'simple_video.mp4'
|
2021-12-21 09:27:45 -07:00
|
|
|
video_path.touch()
|
2022-08-06 05:22:07 +00:00
|
|
|
video_file = File(path=video_path, model='video')
|
|
|
|
|
video = Video(video_file=video_file, channel_id=simple_channel.id)
|
2021-12-21 09:19:26 -07:00
|
|
|
test_session.add(video)
|
|
|
|
|
test_session.commit()
|
|
|
|
|
video = test_session.query(Video).filter_by(id=video.id).one()
|
|
|
|
|
return video
|
2021-12-21 20:57:35 -07:00
|
|
|
|
|
|
|
|
|
2021-12-23 14:09:31 -07:00
|
|
|
@pytest.fixture
|
|
|
|
|
def video_factory(test_session, test_directory):
|
2022-04-07 13:42:01 -06:00
|
|
|
"""Creates Videos for testing."""
|
2022-01-14 15:54:33 -07:00
|
|
|
|
2022-08-06 05:22:07 +00:00
|
|
|
def factory(channel_id: int = None, title: str = None, upload_date=None, with_video_file=None,
|
|
|
|
|
with_info_json: dict = None, with_poster_ext: str = None, with_caption_file: bool = False,
|
|
|
|
|
source_id: str = None):
|
2022-04-08 09:57:04 -06:00
|
|
|
title = title or str(uuid4())
|
2022-08-06 05:22:07 +00:00
|
|
|
|
|
|
|
|
if with_video_file and isinstance(with_video_file, (pathlib.Path, str)):
|
|
|
|
|
# Put the video exactly where specified for the test.
|
|
|
|
|
path = pathlib.Path(with_video_file) if not isinstance(with_video_file, pathlib.Path) else with_video_file
|
|
|
|
|
elif channel_id:
|
|
|
|
|
# Put the video in its Channel's directory.
|
2022-05-10 22:44:59 -06:00
|
|
|
channel = test_session.query(Channel).filter_by(id=channel_id).one()
|
|
|
|
|
path = (channel.directory or test_directory) / f'{title}.mp4'
|
2022-01-05 12:52:23 -07:00
|
|
|
else:
|
2022-08-06 05:22:07 +00:00
|
|
|
# Put any video not in a Channel and without a specified file in the NO CHANNEL directory.
|
|
|
|
|
(test_directory / 'videos/NO CHANNEL').mkdir(exist_ok=True, parents=True)
|
2022-05-03 10:46:10 -06:00
|
|
|
path = test_directory / f'videos/NO CHANNEL/{title}.mp4'
|
2022-01-14 15:54:33 -07:00
|
|
|
|
2022-08-06 05:22:07 +00:00
|
|
|
video_file = File(path=path, model='video')
|
|
|
|
|
test_session.add(video_file)
|
|
|
|
|
|
2022-01-14 15:54:33 -07:00
|
|
|
if with_video_file:
|
|
|
|
|
shutil.copy(PROJECT_DIR / 'test/big_buck_bunny_720p_1mb.mp4', path)
|
2022-08-06 05:22:07 +00:00
|
|
|
else:
|
|
|
|
|
path.touch()
|
2022-01-14 15:54:33 -07:00
|
|
|
|
|
|
|
|
info_json_path = None
|
2022-08-06 05:22:07 +00:00
|
|
|
if with_info_json:
|
2022-01-14 15:54:33 -07:00
|
|
|
# Use the provided object as the json.
|
2022-08-06 05:22:07 +00:00
|
|
|
with_info_json = {'duration': 5} if with_info_json is True else with_info_json
|
2022-01-14 15:54:33 -07:00
|
|
|
info_json_path = path.with_suffix('.info.json')
|
|
|
|
|
info_json_path.write_text(json.dumps(with_info_json))
|
2022-08-06 05:22:07 +00:00
|
|
|
test_session.add(File(path=info_json_path))
|
2022-01-14 15:54:33 -07:00
|
|
|
|
|
|
|
|
poster_path = None
|
|
|
|
|
if with_poster_ext:
|
|
|
|
|
poster_path = path.with_suffix(f'.{with_poster_ext}')
|
|
|
|
|
Image.new('RGB', (25, 25)).save(poster_path)
|
2022-08-06 05:22:07 +00:00
|
|
|
test_session.add(File(path=poster_path))
|
2022-01-14 15:54:33 -07:00
|
|
|
|
2022-04-08 09:57:04 -06:00
|
|
|
caption_path = None
|
|
|
|
|
if with_caption_file:
|
|
|
|
|
caption_path = path.with_suffix('.en.vtt')
|
2022-08-06 05:22:07 +00:00
|
|
|
shutil.copy(PROJECT_DIR / 'test/example1.en.vtt', caption_path)
|
|
|
|
|
test_session.add(File(path=caption_path))
|
|
|
|
|
|
|
|
|
|
video = Video(
|
|
|
|
|
video_path=path,
|
|
|
|
|
title=title,
|
|
|
|
|
channel_id=channel_id,
|
|
|
|
|
source_id=source_id or title,
|
|
|
|
|
info_json_path=info_json_path,
|
|
|
|
|
poster_path=poster_path,
|
|
|
|
|
caption_path=caption_path,
|
|
|
|
|
upload_date=upload_date,
|
|
|
|
|
)
|
2021-12-23 14:09:31 -07:00
|
|
|
test_session.add(video)
|
2022-08-06 05:22:07 +00:00
|
|
|
video_file.do_index()
|
2021-12-23 14:09:31 -07:00
|
|
|
return video
|
|
|
|
|
|
2022-04-08 09:57:04 -06:00
|
|
|
return factory
|
2021-12-30 20:45:11 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def video_download_manager(test_download_manager) -> DownloadManager:
|
2022-07-09 18:51:45 -06:00
|
|
|
"""Get a DownloadManager ready to download Videos and Channels."""
|
2021-12-30 20:45:11 -07:00
|
|
|
channel_downloader = ChannelDownloader()
|
|
|
|
|
video_downloader = VideoDownloader(priority=40)
|
|
|
|
|
|
|
|
|
|
test_download_manager.register_downloader(channel_downloader)
|
|
|
|
|
test_download_manager.register_downloader(video_downloader)
|
|
|
|
|
|
|
|
|
|
with mock.patch('modules.videos.downloader.YDL.extract_info') as mock_extract_info: # prevent real downloads
|
|
|
|
|
mock_extract_info.side_effect = Exception('You must patch modules.videos.downloader.YDL.extract_info!')
|
|
|
|
|
yield test_download_manager
|
2022-02-16 21:57:27 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def test_channels_config(test_directory):
|
|
|
|
|
(test_directory / 'config').mkdir(exist_ok=True)
|
|
|
|
|
config_path = test_directory / 'config/channels.yaml'
|
|
|
|
|
set_test_channels_config(True)
|
|
|
|
|
yield config_path
|
|
|
|
|
set_test_channels_config(False)
|
2022-02-16 23:05:43 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def test_downloader_config(test_directory):
|
|
|
|
|
(test_directory / 'config').mkdir(exist_ok=True)
|
|
|
|
|
config_path = test_directory / 'config/downloader.yaml'
|
|
|
|
|
set_test_downloader_config(True)
|
|
|
|
|
yield config_path
|
|
|
|
|
set_test_downloader_config(False)
|
2022-05-03 10:46:10 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def mock_video_extract_info():
|
|
|
|
|
with mock.patch('modules.videos.downloader.extract_info') as mock_extract_info:
|
|
|
|
|
yield mock_extract_info
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def mock_video_prepare_filename():
|
|
|
|
|
with mock.patch('modules.videos.downloader.prepare_filename') as mock_prepare_filename:
|
|
|
|
|
yield mock_prepare_filename
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def mock_video_process_runner():
|
|
|
|
|
with mock.patch('modules.videos.downloader.VideoDownloader.process_runner') as mock_process_runner:
|
2022-06-26 15:45:45 -06:00
|
|
|
mock_process_runner.return_value = (0, {'stdout': None, 'stderr': None})
|
2022-05-03 10:46:10 -06:00
|
|
|
yield mock_process_runner
|
2022-05-10 22:44:59 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def assert_video_ids(test_session):
|
|
|
|
|
"""Check that only the expected Videos are in the DB."""
|
|
|
|
|
|
|
|
|
|
def checker(expected_video_ids: List[int]):
|
|
|
|
|
video_ids = [i.id for i in test_session.query(Video).order_by(Video.id)]
|
|
|
|
|
for id_, expected in zip_longest(video_ids, expected_video_ids):
|
|
|
|
|
assert id_ == expected, f'Video ids were not as expected: {expected_video_ids=} != {video_ids=}'
|
|
|
|
|
|
|
|
|
|
return checker
|
2022-08-06 05:22:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def video_with_search_factory(test_session, test_directory):
|
|
|
|
|
"""A factory that creates a Video record with an associated video File record."""
|
|
|
|
|
|
|
|
|
|
def video_with_search(path: str = None, title: str = None, b_text: str = None, c_text: str = None,
|
|
|
|
|
d_text: str = None, mimetype: str = 'video/mp4'):
|
|
|
|
|
path = path or (test_directory / str(uuid4())).absolute()
|
|
|
|
|
video_file = File(path=path, a_text=title, d_text=d_text, mimetype=mimetype, model='video')
|
|
|
|
|
video = Video(title=title, video_file=video_file)
|
|
|
|
|
test_session.add(video)
|
|
|
|
|
return video
|
|
|
|
|
|
|
|
|
|
return video_with_search
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def assert_video_search(test_client):
|
|
|
|
|
"""A fixture which performs a video search request against the API.
|
|
|
|
|
|
|
|
|
|
If assert_* params are passed, the response is checked."""
|
|
|
|
|
|
|
|
|
|
def search_videos(
|
|
|
|
|
assert_total: int = None,
|
|
|
|
|
assert_ids: List[int] = None,
|
|
|
|
|
assert_paths: List[str] = None,
|
|
|
|
|
search_str: str = None,
|
|
|
|
|
filters: List[str] = None,
|
|
|
|
|
offset: int = None,
|
|
|
|
|
limit: int = None,
|
|
|
|
|
order_by: str = None,
|
|
|
|
|
channel_id: int = None,
|
|
|
|
|
):
|
|
|
|
|
content = dict()
|
|
|
|
|
if search_str is not None:
|
|
|
|
|
content['search_str'] = search_str
|
|
|
|
|
if filters is not None:
|
|
|
|
|
content['filters'] = filters
|
|
|
|
|
if offset is not None:
|
|
|
|
|
content['offset'] = offset
|
|
|
|
|
if limit is not None:
|
|
|
|
|
content['limit'] = limit
|
|
|
|
|
if order_by is not None:
|
|
|
|
|
content['order_by'] = order_by
|
|
|
|
|
if channel_id is not None:
|
|
|
|
|
content['channel_id'] = channel_id
|
|
|
|
|
|
|
|
|
|
request, response = test_client.post('/api/videos/search', content=json.dumps(content))
|
|
|
|
|
|
|
|
|
|
assert response.json
|
|
|
|
|
assert response.status_code == HTTPStatus.OK
|
|
|
|
|
|
|
|
|
|
if assert_ids is not None:
|
|
|
|
|
assert 'files' in response.json, 'No video files in response'
|
|
|
|
|
assert [i['video']['id'] for i in response.json['files']] == assert_ids, 'Video IDs do not match'
|
|
|
|
|
|
|
|
|
|
if assert_paths is not None:
|
|
|
|
|
assert 'files' in response.json, 'No video files in response'
|
|
|
|
|
assert [i['video']['video_path'] for i in response.json['files']] == assert_paths, \
|
|
|
|
|
'Video paths do not match'
|
|
|
|
|
|
|
|
|
|
if assert_total:
|
|
|
|
|
assert response.json['totals']['files'] == int(assert_total), 'Total video files does not match'
|
|
|
|
|
|
|
|
|
|
return request, response
|
|
|
|
|
|
|
|
|
|
return search_videos
|