diff --git a/tubesync/sync/testdata/README.md b/tubesync/sync/testdata/README.md index 8cdd3c8f..a32ee124 100644 --- a/tubesync/sync/testdata/README.md +++ b/tubesync/sync/testdata/README.md @@ -3,6 +3,6 @@ This directory contains metadata extracted from some test YouTube videos with youtube-dl. -They are used to test (with `sync/tests.py`) the format matchers in `sync/matching.py` +They are used to test (with `sync/tests/test_format_matching.py`) the format matchers in `sync/matching.py` and are not otherwise used in TubeSync. Removing this directory will not break TubeSync but will break test running. diff --git a/tubesync/sync/tests/__init__.py b/tubesync/sync/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tubesync/sync/tests/fixtures.py b/tubesync/sync/tests/fixtures.py new file mode 100644 index 00000000..cf259656 --- /dev/null +++ b/tubesync/sync/tests/fixtures.py @@ -0,0 +1,24 @@ +from django.conf import settings + +metadata_filepath = settings.BASE_DIR / 'sync' / 'testdata' / 'metadata.json' +with open(metadata_filepath, 'rt') as file: + metadata = file.read() +metadata_hdr_filepath = settings.BASE_DIR / 'sync' / 'testdata' / 'metadata_hdr.json' +with open(metadata_hdr_filepath, 'rt') as file: + metadata_hdr = file.read() +metadata_60fps_filepath = settings.BASE_DIR / 'sync' / 'testdata' / 'metadata_60fps.json' +with open(metadata_60fps_filepath, 'rt') as file: + metadata_60fps = file.read() +metadata_60fps_hdr_filepath = settings.BASE_DIR / 'sync' / 'testdata' / 'metadata_60fps_hdr.json' +with open(metadata_60fps_hdr_filepath, 'rt') as file: + metadata_60fps_hdr = file.read() +metadata_20230629_filepath = settings.BASE_DIR / 'sync' / 'testdata' / 'metadata_2023-06-29.json' +with open(metadata_20230629_filepath, 'rt') as file: + metadata_20230629 = file.read() +all_test_metadata = { + 'boring': metadata, + 'hdr': metadata_hdr, + '60fps': metadata_60fps, + '60fps+hdr': metadata_60fps_hdr, + '20230629': metadata_20230629, +} diff --git a/tubesync/sync/tests/test_filepath.py b/tubesync/sync/tests/test_filepath.py new file mode 100644 index 00000000..c58cc226 --- /dev/null +++ b/tubesync/sync/tests/test_filepath.py @@ -0,0 +1,177 @@ +import logging +from pathlib import Path +from django.conf import settings +from django.test import TestCase +from django.utils import timezone +from sync.models import Source, Media +from sync.choices import ( + Val, Fallback, SourceResolution, + YouTube_AudioCodec, YouTube_VideoCodec, + YouTube_SourceType, +) + +from .fixtures import metadata + +class FilepathTestCase(TestCase): + + def setUp(self): + # Disable general logging for test case + logging.disable(logging.CRITICAL) + # Add a test source + self.source = Source.objects.create( + source_type=Val(YouTube_SourceType.CHANNEL), + key='testkey', + name='testname', + directory='testdirectory', + media_format=settings.MEDIA_FORMATSTR_DEFAULT, + index_schedule=3600, + delete_old_media=False, + days_to_keep=14, + source_resolution=Val(SourceResolution.VIDEO_1080P), + source_vcodec=Val(YouTube_VideoCodec.VP9), + source_acodec=Val(YouTube_AudioCodec.OPUS), + prefer_60fps=False, + prefer_hdr=False, + fallback=Val(Fallback.FAIL) + ) + # Add some test media + self.media = Media.objects.create( + key='mediakey', + source=self.source, + metadata=metadata, + ) + + def test_source_media_format(self): + # Check media format validation is working + # Empty + self.source.media_format = '' + self.assertEqual(self.source.get_example_media_format(), '') + # Invalid, bad key + self.source.media_format = '{test}' + self.assertEqual(self.source.get_example_media_format(), '') + # Invalid, extra brackets + self.source.media_format = '{key}}' + self.assertEqual(self.source.get_example_media_format(), '') + # Invalid, not a string + self.source.media_format = 1 + self.assertEqual(self.source.get_example_media_format(), '') + # Check all expected keys validate + self.source.media_format = 'test-{yyyymmdd}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + timezone.now().strftime('%Y%m%d')) + self.source.media_format = 'test-{yyyy_mm_dd}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + timezone.now().strftime('%Y-%m-%d')) + self.source.media_format = 'test-{yyyy_0mm_dd}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + timezone.now().strftime('%Y-0%m-%d')) + self.source.media_format = 'test-{yyyy}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + timezone.now().strftime('%Y')) + self.source.media_format = 'test-{mm}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + timezone.now().strftime('%m')) + self.source.media_format = 'test-{dd}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + timezone.now().strftime('%d')) + self.source.media_format = 'test-{source}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + self.source.slugname) + self.source.media_format = 'test-{source_full}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + self.source.name) + self.source.media_format = 'test-{title}' + self.assertEqual(self.source.get_example_media_format(), + 'test-some-media-title-name') + self.source.media_format = 'test-{title_full}' + self.assertEqual(self.source.get_example_media_format(), + 'test-Some Media Title Name') + self.source.media_format = 'test-{key}' + self.assertEqual(self.source.get_example_media_format(), + 'test-SoMeUnIqUiD') + self.source.media_format = 'test-{format}' + self.assertEqual(self.source.get_example_media_format(), + 'test-1080p-vp9-opus') + self.source.media_format = 'test-{playlist_title}' + self.assertEqual(self.source.get_example_media_format(), + 'test-Some Playlist Title') + self.source.media_format = 'test-{ext}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + self.source.extension) + self.source.media_format = 'test-{resolution}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + self.source.source_resolution) + self.source.media_format = 'test-{height}' + self.assertEqual(self.source.get_example_media_format(), + 'test-720') + self.source.media_format = 'test-{width}' + self.assertEqual(self.source.get_example_media_format(), + 'test-1280') + self.source.media_format = 'test-{vcodec}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + self.source.source_vcodec.lower()) + self.source.media_format = 'test-{acodec}' + self.assertEqual(self.source.get_example_media_format(), + 'test-' + self.source.source_acodec.lower()) + self.source.media_format = 'test-{fps}' + self.assertEqual(self.source.get_example_media_format(), + 'test-24') + self.source.media_format = 'test-{hdr}' + self.assertEqual(self.source.get_example_media_format(), + 'test-hdr') + + def test_media_filename(self): + # Check child directories work + self.source.media_format = '{yyyy}/{key}.{ext}' + self.assertEqual(self.media.directory_path, + self.source.directory_path / '2017') + self.assertEqual(self.media.filename, '2017/mediakey.mkv') + self.source.media_format = '{yyyy}/{yyyy_mm_dd}/{key}.{ext}' + self.assertEqual(self.media.directory_path, + self.source.directory_path / '2017/2017-09-11') + self.assertEqual(self.media.filename, '2017/2017-09-11/mediakey.mkv') + # Check media specific media format keys work + test_media = Media.objects.create( + key='test', + source=self.source, + metadata=metadata, + downloaded=True, + download_date=timezone.now(), + downloaded_format='720p', + downloaded_height=720, + downloaded_width=1280, + downloaded_audio_codec='opus', + downloaded_video_codec='vp9', + downloaded_container='mkv', + downloaded_fps=30, + downloaded_hdr=True, + downloaded_filesize=12345 + ) + # Bypass media-file-exists on-save signal + test_media.downloaded = True + self.source.media_format = ('{title}_{key}_{resolution}-{height}x{width}-' + '{acodec}-{vcodec}-{fps}fps-{hdr}.{ext}') + self.assertEqual(test_media.filename, + ('no-fancy-stuff-title_test_720p-720x1280-opus' + '-vp9-30fps-hdr.mkv')) + + def test_directory_prefix(self): + # Confirm the setting exists and is valid + self.assertTrue(hasattr(settings, 'SOURCE_DOWNLOAD_DIRECTORY_PREFIX')) + self.assertTrue(isinstance(settings.SOURCE_DOWNLOAD_DIRECTORY_PREFIX, bool)) + # Test the default behavior for "True", forced "audio" or "video" parent directories for sources + settings.SOURCE_DOWNLOAD_DIRECTORY_PREFIX = True + self.source.source_resolution = Val(SourceResolution.AUDIO) + test_audio_prefix_path = Path(self.source.directory_path) + self.assertEqual(test_audio_prefix_path.parts[-2], 'audio') + self.assertEqual(test_audio_prefix_path.parts[-1], 'testdirectory') + self.source.source_resolution = Val(SourceResolution.VIDEO_1080P) + test_video_prefix_path = Path(self.source.directory_path) + self.assertEqual(test_video_prefix_path.parts[-2], 'video') + self.assertEqual(test_video_prefix_path.parts[-1], 'testdirectory') + # Test the default behavior for "False", no parent directories for sources + settings.SOURCE_DOWNLOAD_DIRECTORY_PREFIX = False + test_no_prefix_path = Path(self.source.directory_path) + self.assertEqual(test_no_prefix_path.parts[-1], 'testdirectory') + + diff --git a/tubesync/sync/tests.py b/tubesync/sync/tests/test_format_matching.py similarity index 64% rename from tubesync/sync/tests.py rename to tubesync/sync/tests/test_format_matching.py index 62883068..965e7acd 100644 --- a/tubesync/sync/tests.py +++ b/tubesync/sync/tests/test_format_matching.py @@ -1,935 +1,13 @@ -''' - Tests do not test the scheduled tasks that perform live requests to index media or - download content. They only check for compliance of web interface and check - the matching code logic is working as expected. -''' - - import logging -from datetime import datetime, timedelta -from pathlib import Path -from urllib.parse import urlsplit -from xml.etree import ElementTree -from django.conf import settings -from django.test import TestCase, Client, override_settings -from django.utils import timezone -from django_huey import DJANGO_HUEY, get_queue -from common.models import TaskHistory -from .models import Source, Media -from .tasks import ( - cleanup_old_media, check_source_directory_exists, - get_media_download_task, get_media_thumbnail_task, -) -from .filtering import filter_media -from .utils import filter_response -from .choices import ( - Val, Fallback, FilterSeconds, IndexSchedule, SourceResolution, - TaskQueue, YouTube_AudioCodec, YouTube_VideoCodec, +from django.test import TestCase +from sync.models import Source, Media +from sync.choices import ( + Val, Fallback, SourceResolution, + YouTube_AudioCodec, YouTube_VideoCodec, YouTube_SourceType, ) - -class FrontEndTestCase(TestCase): - maxDiff = None - - @classmethod - def setUpClass(cls): - super().setUpClass() - # Use immediate mode to execute tasks in this process - for qn in DJANGO_HUEY.get('queues', dict()): - q = get_queue(qn) - # Set the storage variable before using the property. - q.immediate_use_memory = True - q.immediate = False - - def setUp(self): - # Disable general logging for test case - logging.disable(logging.CRITICAL) - - def test_dashboard(self): - c = Client() - response = c.get('/') - self.assertEqual(response.status_code, 200) - - def test_validate_source(self): - test_sources = { - 'youtube-channel': { - 'valid': ( - 'https://m.youtube.com/testchannel', - 'https://m.youtube.com/c/testchannel', - 'https://m.youtube.com/c/testchannel/videos', - 'https://www.youtube.com/testchannel', - 'https://www.youtube.com/c/testchannel', - 'https://www.youtube.com/c/testchannel/videos', - ), - 'invalid_schema': ( - 'http://www.youtube.com/c/playlist', - 'ftp://www.youtube.com/c/playlist', - ), - 'invalid_domain': ( - 'https://www.test.com/c/testchannel', - 'https://www.example.com/c/testchannel', - 'https://n.youtube.com/c/testchannel', - ), - 'invalid_path': ( - 'https://www.youtube.com/test/invalid', - 'https://www.youtube.com/c/test/invalid', - ), - 'invalid_reserved_paths': ( - 'https://www.youtube.com/watch?v=OkMadb8cpIw', - 'https://www.youtube.com/watch', - 'https://www.youtube.com/shorts', - 'https://www.youtube.com/live', - 'https://www.youtube.com/feed', - 'https://www.youtube.com/trending', - ), - }, - 'youtube-channel-id': { - 'valid': ( - 'https://m.youtube.com/channel/channelid', - 'https://m.youtube.com/channel/channelid/videos', - 'https://www.youtube.com/channel/channelid', - 'https://www.youtube.com/channel/channelid/videos', - ), - 'invalid_schema': ( - 'http://www.youtube.com/channel/channelid', - 'ftp://www.youtube.com/channel/channelid', - ), - 'invalid_domain': ( - 'https://www.test.com/channel/channelid', - 'https://www.example.com/channel/channelid', - 'https://n.youtube.com/channel/channelid', - ), - 'invalid_path': ( - 'https://www.youtube.com/test/invalid', - 'https://www.youtube.com/channel/test/invalid', - ), - }, - 'youtube-playlist': { - 'valid': ( - 'https://m.youtube.com/playlist?list=testplaylist', - 'https://m.youtube.com/watch?v=testvideo&list=testplaylist', - 'https://www.youtube.com/playlist?list=testplaylist', - 'https://www.youtube.com/watch?v=testvideo&list=testplaylist', - ), - 'invalid_schema': ( - 'http://www.youtube.com/playlist?list=testplaylist', - 'ftp://www.youtube.com/playlist?list=testplaylist', - ), - 'invalid_domain': ( - 'https://www.test.com/playlist?list=testplaylist', - 'https://www.example.com/playlist?list=testplaylist', - 'https://n.youtube.com/playlist?list=testplaylist', - ), - 'invalid_path': ( - 'https://www.youtube.com/test/invalid', - ), - } - } - c = Client() - response = c.get('/source-validate') - self.assertEqual(response.status_code, 200) - for (source_type, tests) in test_sources.items(): - for test, urls in tests.items(): - for url in urls: - data = {'source_url': url} - response = c.post('/source-validate', data) - if test == 'valid': - # Valid source tests should bounce to /source-add - self.assertEqual(response.status_code, 302) - url_parts = urlsplit(response.url) - self.assertEqual(url_parts.path, '/source-add') - else: - # Invalid source tests should reload the page with an error - self.assertEqual(response.status_code, 200) - self.assertIn('