mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
After a Channel refresh, log how many video/audio FileGroups are in the Channel's directory, and how many are assigned to this Channel, other Channels, or no Channel. Helps debug Videos missing from their Channel after files are moved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
200 lines
7.8 KiB
Python
200 lines
7.8 KiB
Python
import json
|
|
from http import HTTPStatus
|
|
|
|
import pytest
|
|
|
|
from modules.videos.models import Channel
|
|
from modules.videos.models import Video
|
|
from wrolpi.downloader import Download, DownloadFrequency
|
|
|
|
|
|
def test_delete_channel_no_url(test_session, channel_factory):
|
|
"""
|
|
A Channel can be deleted even if it has no URL.
|
|
"""
|
|
channel = channel_factory()
|
|
channel.url = None
|
|
test_session.commit()
|
|
|
|
channel.delete_with_videos()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_channel_with_videos(test_session, async_client, channel_factory, video_factory):
|
|
"""Videos are disowned when their Channel is deleted. The Collection should also be deleted."""
|
|
from wrolpi.collections import Collection
|
|
|
|
channel = channel_factory()
|
|
video = video_factory(channel_id=channel.id)
|
|
video_path, video_id = video.video_path, video.id
|
|
collection_id = channel.collection_id
|
|
test_session.commit()
|
|
|
|
# Delete the Channel.
|
|
request, response = await async_client.delete(f'/api/videos/channels/{channel.id}')
|
|
assert response.status_code == HTTPStatus.NO_CONTENT
|
|
|
|
# Video still exists, but has no channel.
|
|
video: Video = test_session.query(Video).one()
|
|
assert video.video_path == video_path and video.id == video_id, 'Video entry should not be changed'
|
|
assert video.video_path.is_file(), 'Video should not be deleted'
|
|
assert not video.channel_id and not video.channel, 'Video should not have a Channel'
|
|
|
|
# Collection should also be deleted (this is the bug we're fixing)
|
|
assert test_session.query(Collection).filter_by(id=collection_id).count() == 0, \
|
|
'Collection should be deleted when Channel is deleted'
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_nested_channel_directories(test_session, async_client, test_directory, channel_factory,
|
|
video_factory):
|
|
"""Channel directories cannot contain another Channel's directory."""
|
|
(test_directory / 'foo').mkdir()
|
|
channel1_directory = test_directory / 'foo/one'
|
|
channel_factory(directory=channel1_directory)
|
|
test_session.commit()
|
|
|
|
# Channel 2 cannot be in Channel 1's directory.
|
|
channel2_directory = channel1_directory / 'two'
|
|
channel2_directory.mkdir()
|
|
content = dict(
|
|
name='channel 2',
|
|
directory=str(channel2_directory),
|
|
)
|
|
request, response = await async_client.post('/api/videos/channels', content=json.dumps(content))
|
|
assert response.status_code == HTTPStatus.BAD_REQUEST
|
|
assert response.json['cause'] and \
|
|
response.json['cause']['message'] == 'The directory is already used by another channel.'
|
|
|
|
# Channel 3 cannot be above Channel 1's directory.
|
|
content = dict(
|
|
name='channel 3',
|
|
directory=str(test_directory),
|
|
)
|
|
request, response = await async_client.post('/api/videos/channels', content=json.dumps(content))
|
|
assert response.status_code == HTTPStatus.BAD_REQUEST
|
|
assert response.json['cause'] and \
|
|
response.json['cause']['message'] == 'The directory is already used by another channel.'
|
|
|
|
|
|
@pytest.mark.asyncio()
|
|
async def test_channel_download_relationships(test_session, download_channel):
|
|
"""Test relationships of Channel and Download."""
|
|
test_session.flush()
|
|
|
|
download = test_session.query(Download).one()
|
|
channel = test_session.query(Channel).one()
|
|
assert len(channel.downloads) == 1
|
|
assert channel.downloads
|
|
assert download in channel.downloads
|
|
|
|
test_session.commit()
|
|
|
|
download: Download = test_session.query(Download).one()
|
|
channel: Channel = test_session.query(Channel).one()
|
|
assert len(channel.downloads) == 1
|
|
assert channel.downloads[0] == download and channel.downloads[0].url == 'https://example.com/channel1'
|
|
assert download.frequency == DownloadFrequency.weekly
|
|
assert download.destination == channel.directory
|
|
|
|
# Deleting Download deletes the Download, but not the Channel.
|
|
download.delete()
|
|
assert not test_session.query(Download).all()
|
|
assert test_session.query(Channel).one(), 'Channel should not have been deleted.'
|
|
|
|
# Create a Download again.
|
|
download_channel.get_or_create_download(test_session, 'https://example.com/1', 60, reset_attempts=True)
|
|
assert test_session.query(Download).count() == 1
|
|
# Delete the Channel, and any relationships.
|
|
download_channel.delete_with_videos()
|
|
assert test_session.query(Download).count() == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_channel_statistics_with_tagged_videos(async_client, test_session, channel_factory, video_factory,
|
|
tag_factory):
|
|
"""Tagging a Video must not inflate its Channel's statistics.
|
|
|
|
A Video with multiple Tags was counted once per Tag because of the tag_file join."""
|
|
channel = channel_factory()
|
|
video1 = video_factory(channel_id=channel.id)
|
|
video2 = video_factory(channel_id=channel.id)
|
|
video3 = video_factory(channel_id=channel.id)
|
|
test_session.commit()
|
|
|
|
expected_size = video1.file_group.size + video2.file_group.size + video3.file_group.size
|
|
|
|
# Statistics are correct before any Videos are tagged.
|
|
statistics = channel.get_statistics()
|
|
assert statistics['video_count'] == 3
|
|
assert statistics['size'] == expected_size
|
|
assert statistics['video_tags'] == 0
|
|
|
|
# video1 has two Tags, video2 has one Tag, video3 has none.
|
|
tag1, tag2 = await tag_factory(), await tag_factory()
|
|
video1.add_tag(test_session, tag1.name)
|
|
video1.add_tag(test_session, tag2.name)
|
|
video2.add_tag(test_session, tag1.name)
|
|
test_session.commit()
|
|
|
|
# Tagging Videos must not change the number of Videos in the Channel.
|
|
statistics = channel.get_statistics()
|
|
assert statistics['video_count'] == 3
|
|
assert statistics['size'] == expected_size
|
|
assert statistics['video_tags'] == 2
|
|
|
|
|
|
def test_channel_directory_video_counts(test_session, channel_factory, video_factory):
|
|
"""A Channel can count the video FileGroups in its directory, and which Channel they are assigned to.
|
|
|
|
These counts are logged after a Channel refresh to help debug Videos missing from their Channel."""
|
|
channel1 = channel_factory()
|
|
channel2 = channel_factory()
|
|
video_factory(channel_id=channel1.id)
|
|
video_factory(channel_id=channel1.id)
|
|
# These Videos' files are in channel1's directory, but they are not assigned to channel1.
|
|
other_channel_video = video_factory(with_video_file=channel1.directory / 'other channel.mp4')
|
|
unassigned_video = video_factory(with_video_file=channel1.directory / 'unassigned.mp4')
|
|
other_channel_video.channel_id = channel2.id
|
|
unassigned_video.channel_id = None
|
|
test_session.commit()
|
|
|
|
assert channel1.get_directory_video_counts() == dict(
|
|
video_file_groups=4,
|
|
assigned_to_channel=2,
|
|
assigned_to_other_channels=1,
|
|
unassigned=1,
|
|
)
|
|
|
|
# channel2's directory contains no files.
|
|
assert channel2.get_directory_video_counts() == dict(
|
|
video_file_groups=0,
|
|
assigned_to_channel=0,
|
|
assigned_to_other_channels=0,
|
|
unassigned=0,
|
|
)
|
|
|
|
|
|
def test_channel_info_json(test_session, test_directory):
|
|
from wrolpi.collections import Collection
|
|
|
|
# Create Collection first without directory
|
|
collection = Collection(name='new channel', kind='channel')
|
|
test_session.add(collection)
|
|
test_session.flush()
|
|
|
|
channel = Channel(collection_id=collection.id)
|
|
test_session.add(channel)
|
|
test_session.flush()
|
|
|
|
with pytest.raises(FileNotFoundError) as e:
|
|
# Cannot get info json because directory is not defined.
|
|
channel.info_json_path
|
|
assert 'directory' in str(e)
|
|
|
|
# Channel's directory is created if it does not exist.
|
|
channel.directory = test_directory / 'new channel'
|
|
assert channel.info_json_path == test_directory / 'new channel/new channel.info.json'
|
|
assert channel.directory.is_dir()
|
|
# info json file is not yet created.
|
|
assert not channel.info_json_path.exists()
|