wrolpi/modules/videos/test/test_api.py
Roland d0d69519cd
Bumping version for new feature.
Skipping video epoch test on circleci.

Created script which finds and cleans up (renames/deletes) orphaned video meta files.

Reworking orphaned video query, some video files were leaking through.

Modifying File table to allow finding of orphaned video files.

Extracting epoch from video info_json for more accurate upload_date.

Extracting epoch from video info_json for more accurate upload_date.
2022-12-07 18:15:28 -07:00

228 lines
9.5 KiB
Python

import json
import shutil
from http import HTTPStatus
from pathlib import Path
import pytest
from modules.videos.models import Video, Channel
from modules.videos.video import lib as video_lib
from wrolpi.downloader import DownloadFrequency
from wrolpi.files.lib import refresh_files
from wrolpi.files.models import File
from wrolpi.vars import PROJECT_DIR
@pytest.mark.asyncio
async def test_refresh_videos_index(test_session, test_directory, video_factory):
"""A video discovered during refresh has files associated with it. Only the video file is indexed."""
video_factory(with_video_file=True, with_caption_file=True, with_poster_ext='jpg', with_info_json=True)
test_session.commit()
test_session.query(File).delete()
test_session.commit()
for _ in range(1):
# Refreshing twice does not change the results.
await refresh_files()
assert test_session.query(File).count() == 4
video: Video = test_session.query(Video).one()
assert video.video_file.indexed is True, 'Video was not indexed'
assert video.video_file.a_text, 'Video title was not indexed'
assert video.video_file.d_text, 'Video captions were not indexed'
assert not video.poster_file.indexed
assert not video.caption_file.indexed
assert not video.info_json_file.indexed
# Delete caption file, the video should be indexed again.
video: Video = test_session.query(Video).one()
video.caption_path.unlink()
await refresh_files()
assert video.video_file.indexed is True, 'Video was not indexed'
assert video.video_file.a_text, 'Video title was not indexed'
assert not video.video_file.d_text, 'Video captions were not removed'
def test_refresh_videos(test_client, test_session, test_directory, simple_channel, video_factory):
subdir = test_directory / 'subdir'
subdir.mkdir()
# video1 is in a subdirectory, move its files into the subdirectory.
video1 = video_factory(channel_id=simple_channel.id, with_video_file=subdir / 'video1.mp4',
with_info_json=True,
with_poster_ext='jpg')
test_session.commit()
test_session.delete(video1.poster_file)
video1.poster_path = None
# video2 is in the test directory.
video2 = video_factory(channel_id=simple_channel.id, with_video_file=True, with_info_json=True,
with_poster_ext='jpg')
video2.poster_path = video2.poster_file = video1.poster_path = None
test_session.commit()
assert not video1.size, 'video1 should not have size during creation'
# Create a video not in the DB.
vid3 = test_directory / 'vid3.mp4'
shutil.copy(PROJECT_DIR / 'test/big_buck_bunny_720p_1mb.mp4', vid3)
# Orphan poster should be ignored.
orphan_poster = Path(test_directory / 'channel name_20000104_defghijklmn_title.jpg')
orphan_poster.touch()
# Create a bogus video in the channel.
video_file = File(path=test_directory / 'foo')
video_file.idempotency = None # File was not added in the current refresh.
bogus = Video(video_file=video_file, channel_id=simple_channel.id)
test_session.add(bogus)
test_session.commit()
test_client.post('/api/files/refresh')
# Posters were found during refresh.
assert video1.poster_path
assert 'subdir' in str(video1.poster_path)
assert video2.poster_path
# Missing video3 was found
video3: Video = test_session.query(Video).filter_by(id=4).one()
assert video3.video_path == vid3
assert len(set(test_session.query(Video.video_path))) == 3, 'Should have 3 distinct video files'
# Bogus video was removed.
assert not any('foo' in str(i.video_path) for i in test_session.query(Video).all())
# Orphan file was not deleted.
assert orphan_poster.is_file(), 'Orphan poster was removed!'
assert video1.size, 'video1 size was not found'
# Remove video1's poster, video1 should be updated.
video1_video_path = str(video1.video_path)
video1.poster_path.unlink()
test_client.post('/api/files/refresh')
video1 = test_session.query(Video).filter_by(video_path=video1_video_path).one()
assert not video1.poster_path
def test_channels_with_videos(test_session, test_client, test_directory, channel_factory, video_factory):
channel1 = channel_factory('channel1', name='channel1')
channel2 = channel_factory('channel2', name='channel2')
vid1 = video_factory(channel_id=channel1.id, with_video_file=True)
vid2 = video_factory(channel_id=channel2.id, with_video_file=True)
vid3 = video_factory(with_video_file=True)
test_session.commit()
vid1_path = vid1.video_path
vid2_path = vid2.video_path
vid3_path = vid3.video_path
test_session.delete(vid1)
test_session.delete(vid2)
test_session.delete(vid3)
test_session.commit()
assert test_session.query(Video).count() == 0, 'Videos were not deleted'
assert test_session.query(Channel).count() == 2, 'Channels were deleted!'
assert vid1_path.is_file(), 'Video file was deleted'
assert vid2_path.is_file(), 'Video file was deleted'
assert vid3_path.is_file(), 'Video file was deleted'
test_client.post('/api/files/refresh')
assert test_session.query(Video).count() == 3, 'Did not find correct amount of video files.'
assert {i[0] for i in test_session.query(Channel.name)} == {'channel1', 'channel2'}, 'Channels were changed.'
vid1: Video = test_session.query(Video).filter_by(video_path=vid1_path).one()
vid2: Video = test_session.query(Video).filter_by(video_path=vid2_path).one()
vid3: Video = test_session.query(Video).filter_by(video_path=vid3_path).one()
assert vid1.channel == channel1
assert vid2.channel == channel2
assert vid3.channel is None
def test_api_download_channel(test_session, test_client, simple_channel):
"""A Channel download (a catalog update) can be triggered via the API."""
# simple_channel does not have a download record.
request, response = test_client.post(f'/api/videos/download/{simple_channel.id}')
assert response.status_code == HTTPStatus.BAD_REQUEST, response.json
assert 'not have a download' in response.json['message']
# Add a download frequency to the channel, this should also create a download.
simple_channel.update(dict(download_frequency=DownloadFrequency.daily))
test_session.commit()
request, response = test_client.post(f'/api/videos/download/{simple_channel.id}')
assert response.status_code == HTTPStatus.NO_CONTENT
def test_search_videos_file(test_client, test_session, test_directory, video_with_search_factory):
"""Test that videos can be searched and that their order is by their textsearch rank."""
# These captions have repeated letters, so they will be higher in the ranking.
videos = [
('1', 'b b b b e d d'),
('2', '2 b b b d'),
('3', 'b b'),
('4', 'b e e'),
('5', ''),
]
for title, caption in videos:
video_with_search_factory(title=title, d_text=caption)
test_session.commit()
def assert_search(search_str, expected, limit=20, total=None):
d = json.dumps({'search_str': search_str, 'limit': limit})
_, resp = test_client.post('/api/videos/search', content=d)
assert resp.status_code == HTTPStatus.OK
response_ids = [i['video']['id'] for i in resp.json['files']]
assert response_ids == expected
assert resp.json['totals']['files'] == (total or len(expected))
# Repeated runs should return the same result
for _ in range(2):
# Only videos with a b are returned, ordered by the amount of b's
assert_search('b', [1, 2, 3, 4])
# Only two captions have e
assert_search('e', [4, 1])
# Only two captions have d
assert_search('d', [1, 2])
# 5 can be gotten by it's title
assert_search('5', [5])
# only video 1 has e and d
assert_search('e d', [1])
# video 1 and 4 have b and e, but 1 has more
assert_search('b e', [1, 4])
# Check totals are correct even with a limit
assert_search('b', [1, 2], limit=2, total=4)
def test_search_videos(test_client, test_session, video_factory, assert_video_search, simple_channel):
"""Search the Video table. This does not need to use a join with the File table."""
vid1 = video_factory(upload_date='2022-09-16', with_video_file=True)
vid2 = video_factory(upload_date='2022-09-17', with_video_file=True, channel_id=simple_channel.id)
vid3 = video_factory(upload_date='2022-09-18', with_video_file=True)
vid1.set_favorite(True)
test_session.commit()
assert test_session.query(Video).count() == 3
assert test_session.query(File).count() == 3
assert_video_search(assert_total=3, assert_ids=[vid1.id, vid2.id, vid3.id], order_by='upload_date')
assert_video_search(assert_total=3, assert_ids=[vid3.id, vid2.id, vid1.id], order_by='-upload_date')
assert_video_search(assert_total=3, assert_ids=[vid3.id, vid2.id], order_by='-upload_date', limit=2)
assert_video_search(assert_total=3, assert_ids=[vid2.id, vid1.id], order_by='-upload_date', limit=2, offset=1)
assert_video_search(assert_total=1, assert_ids=[vid2.id], order_by='-upload_date', channel_id=simple_channel.id)
assert_video_search(assert_total=1, assert_ids=[vid1.id], filters=['favorite'])
assert_video_search(assert_total=1, assert_ids=[vid1.id], filters=['favorite'], limit=1)
# No results, no total is returned from the DB.
assert_video_search(assert_total=0, assert_ids=[], filters=['favorite'], offset=1)
# Check all order_by.
for order_by in video_lib.VIDEO_ORDERS:
assert_video_search(order_by=order_by)