feat: add upload options for pre-merge, merged, and standalone chat videos
- Updated global schema to include options for uploading original videos before merging, merged videos, and standalone chat videos. - Modified constants to set default values for new upload options. - Enhanced FileManager to handle new upload options, including conditional file uploads and deletions based on user configuration. - Introduced unit tests for command-line argument parsing, configuration loading, and merging logic, ensuring robust handling of new features. - Added tests for filtering logic, default configurations, and enabled streamer handling.
This commit is contained in:
parent
38d51636af
commit
0d3cdfd12c
6 changed files with 1128 additions and 23 deletions
448
test_twitch_archive_simple.py
Normal file
448
test_twitch_archive_simple.py
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
"""
|
||||
Unit tests for Twitch Archive command-line options and configuration.
|
||||
|
||||
Tests focus on:
|
||||
- Command-line argument parsing (via getopt simulation)
|
||||
- Options and option combinations
|
||||
- Configuration logic (filtering, merging, etc.)
|
||||
- Mode selection logic
|
||||
|
||||
Excludes actual download/processing functionality.
|
||||
|
||||
To run these tests:
|
||||
python test_twitch_archive_simple.py
|
||||
or
|
||||
python -m pytest test_twitch_archive_simple.py -v
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import getopt
|
||||
from unittest.mock import patch, MagicMock, Mock
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from modules.constants import DEFAULT_CONFIG
|
||||
|
||||
|
||||
class TestCommandLineArgumentParsing(unittest.TestCase):
|
||||
"""Test command-line argument parsing logic using getopt directly."""
|
||||
|
||||
def test_help_short_option(self):
|
||||
"""Test -h option parsing."""
|
||||
argv = ['-h']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
# Should parse successfully
|
||||
self.assertEqual(len(opts), 1)
|
||||
self.assertEqual(opts[0][0], '-h')
|
||||
|
||||
def test_help_long_option(self):
|
||||
"""Test --help option parsing."""
|
||||
argv = ['--help']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 1)
|
||||
self.assertEqual(opts[0][0], '--help')
|
||||
|
||||
def test_username_short_option(self):
|
||||
"""Test -u username option parsing."""
|
||||
argv = ['-u', 'teststreamer']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 1)
|
||||
self.assertEqual(opts[0], ('-u', 'teststreamer'))
|
||||
|
||||
def test_username_long_option(self):
|
||||
"""Test --username option parsing."""
|
||||
argv = ['--username', 'teststreamer']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 1)
|
||||
self.assertEqual(opts[0], ('--username', 'teststreamer'))
|
||||
|
||||
def test_verbose_option(self):
|
||||
"""Test --verbose option parsing."""
|
||||
argv = ['--verbose']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 1)
|
||||
self.assertEqual(opts[0], ('--verbose', ''))
|
||||
|
||||
def test_chat_only_option(self):
|
||||
"""Test --chat-only option parsing."""
|
||||
argv = ['--chat-only']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 1)
|
||||
self.assertEqual(opts[0], ('--chat-only', ''))
|
||||
|
||||
def test_legacy_option(self):
|
||||
"""Test --legacy option parsing."""
|
||||
argv = ['--legacy']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 1)
|
||||
self.assertEqual(opts[0], ('--legacy', ''))
|
||||
|
||||
def test_chat_downloader_options(self):
|
||||
"""Test chat downloader option parsing."""
|
||||
argv = ['--use-chat-downloader-primary', '--no-chat-downloader-fallback']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 2)
|
||||
self.assertEqual(opts[0], ('--use-chat-downloader-primary', ''))
|
||||
self.assertEqual(opts[1], ('--no-chat-downloader-fallback', ''))
|
||||
|
||||
def test_legacy_quality_option(self):
|
||||
"""Test -q quality option parsing."""
|
||||
argv = ['-q', '720p']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 1)
|
||||
self.assertEqual(opts[0], ('-q', '720p'))
|
||||
|
||||
def test_legacy_boolean_options(self):
|
||||
"""Test legacy boolean option parsing."""
|
||||
argv = ['-v', '1', '-c', '0', '-m', '1']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 3)
|
||||
self.assertEqual(opts[0], ('-v', '1'))
|
||||
self.assertEqual(opts[1], ('-c', '0'))
|
||||
self.assertEqual(opts[2], ('-m', '1'))
|
||||
|
||||
def test_invalid_option(self):
|
||||
"""Test that invalid option raises error."""
|
||||
argv = ['--invalid-option']
|
||||
|
||||
with self.assertRaises(getopt.GetoptError):
|
||||
getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
def test_option_combination_username_verbose(self):
|
||||
"""Test combining -u and --verbose options."""
|
||||
argv = ['-u', 'testuser', '--verbose']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 2)
|
||||
self.assertEqual(opts[0], ('-u', 'testuser'))
|
||||
self.assertEqual(opts[1], ('--verbose', ''))
|
||||
|
||||
def test_option_combination_all_test_flags(self):
|
||||
"""Test combining all test-related flags."""
|
||||
argv = ['-u', 'testuser', '--verbose', '--chat-only', '--use-chat-downloader-primary']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 4)
|
||||
opt_dict = dict(opts)
|
||||
self.assertEqual(opt_dict['-u'], 'testuser')
|
||||
self.assertIn('--verbose', opt_dict)
|
||||
self.assertIn('--chat-only', opt_dict)
|
||||
self.assertIn('--use-chat-downloader-primary', opt_dict)
|
||||
|
||||
def test_option_combination_legacy_mode_with_overrides(self):
|
||||
"""Test legacy mode with multiple overrides."""
|
||||
argv = ['--legacy', '-q', '720p', '-v', '1', '-c', '1', '-m', '0']
|
||||
opts, args = getopt.getopt(
|
||||
argv,
|
||||
"hu:q:a:v:c:m:r:d:n:",
|
||||
["help", "username=", "quality=", "ttv-lol=", "vod=", "chat=",
|
||||
"metadata=", "upload=", "delete=", "notifications=", "legacy", "verbose",
|
||||
"chat-only", "use-chat-downloader-primary", "no-chat-downloader-fallback"]
|
||||
)
|
||||
|
||||
self.assertEqual(len(opts), 5)
|
||||
opt_dict = dict(opts)
|
||||
self.assertIn('--legacy', opt_dict)
|
||||
self.assertEqual(opt_dict['-q'], '720p')
|
||||
self.assertEqual(opt_dict['-v'], '1')
|
||||
self.assertEqual(opt_dict['-c'], '1')
|
||||
self.assertEqual(opt_dict['-m'], '0')
|
||||
|
||||
|
||||
class TestOptionLogicProcessing(unittest.TestCase):
|
||||
"""Test the logic that processes parsed options."""
|
||||
|
||||
def test_boolean_conversion_true(self):
|
||||
"""Test converting '1' to boolean True."""
|
||||
value = '1'
|
||||
result = bool(int(value))
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_boolean_conversion_false(self):
|
||||
"""Test converting '0' to boolean False."""
|
||||
value = '0'
|
||||
result = bool(int(value))
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_chat_only_auto_enables_verbose(self):
|
||||
"""Test that chat-only mode should auto-enable verbose."""
|
||||
# Simulate the logic from main()
|
||||
chat_only_mode = True
|
||||
verbose_mode = False
|
||||
|
||||
if chat_only_mode:
|
||||
verbose_mode = True
|
||||
|
||||
self.assertTrue(verbose_mode)
|
||||
|
||||
def test_default_chat_downloader_fallback(self):
|
||||
"""Test that chat downloader fallback defaults to enabled."""
|
||||
use_chat_downloader_fallback = True # Default value
|
||||
|
||||
# Unless explicitly disabled
|
||||
self.assertTrue(use_chat_downloader_fallback)
|
||||
|
||||
def test_mode_selection_legacy_with_config_json(self):
|
||||
"""Test mode selection logic when config.json exists."""
|
||||
# Simulate conditions
|
||||
use_legacy_mode = False
|
||||
legacy_config_exists = True
|
||||
specific_streamer = None
|
||||
global_config_exists = False
|
||||
|
||||
# Logic from main() function
|
||||
should_use_legacy = use_legacy_mode or (
|
||||
legacy_config_exists and not specific_streamer and not global_config_exists
|
||||
)
|
||||
|
||||
self.assertTrue(should_use_legacy)
|
||||
|
||||
def test_mode_selection_multi_streamer_with_global_json(self):
|
||||
"""Test mode selection logic when global.json exists."""
|
||||
use_legacy_mode = False
|
||||
legacy_config_exists = True
|
||||
specific_streamer = None
|
||||
global_config_exists = True
|
||||
|
||||
should_use_legacy = use_legacy_mode or (
|
||||
legacy_config_exists and not specific_streamer and not global_config_exists
|
||||
)
|
||||
|
||||
self.assertFalse(should_use_legacy)
|
||||
|
||||
def test_mode_selection_multi_streamer_with_username_flag(self):
|
||||
"""Test mode selection when -u flag is used."""
|
||||
use_legacy_mode = False
|
||||
legacy_config_exists = True
|
||||
specific_streamer = 'testuser'
|
||||
global_config_exists = False
|
||||
|
||||
should_use_legacy = use_legacy_mode or (
|
||||
legacy_config_exists and not specific_streamer and not global_config_exists
|
||||
)
|
||||
|
||||
self.assertFalse(should_use_legacy)
|
||||
|
||||
def test_mode_selection_explicit_legacy_flag(self):
|
||||
"""Test mode selection with explicit --legacy flag."""
|
||||
use_legacy_mode = True
|
||||
legacy_config_exists = False
|
||||
specific_streamer = None
|
||||
global_config_exists = True
|
||||
|
||||
should_use_legacy = use_legacy_mode or (
|
||||
legacy_config_exists and not specific_streamer and not global_config_exists
|
||||
)
|
||||
|
||||
self.assertTrue(should_use_legacy)
|
||||
|
||||
|
||||
class TestConfigLogic(unittest.TestCase):
|
||||
"""Test configuration management logic."""
|
||||
|
||||
def test_comment_filtering_logic(self):
|
||||
"""Test that comment fields are filtered out."""
|
||||
user_config = {
|
||||
'_comment': 'This is a comment',
|
||||
'quality': '720p',
|
||||
'_note': 'Another comment',
|
||||
'username': 'testuser'
|
||||
}
|
||||
|
||||
# Apply the filtering logic (same as in ConfigManager)
|
||||
filtered = {k: v for k, v in user_config.items() if not k.startswith('_')}
|
||||
|
||||
self.assertNotIn('_comment', filtered)
|
||||
self.assertNotIn('_note', filtered)
|
||||
self.assertIn('quality', filtered)
|
||||
self.assertIn('username', filtered)
|
||||
self.assertEqual(filtered['quality'], '720p')
|
||||
|
||||
def test_schema_filtering_logic(self):
|
||||
"""Test that $schema field is filtered out."""
|
||||
user_config = {
|
||||
'$schema': './config.schema.json',
|
||||
'quality': '720p',
|
||||
'username': 'testuser'
|
||||
}
|
||||
|
||||
# Apply the filtering logic (same as in ConfigManager)
|
||||
filtered = {k: v for k, v in user_config.items()
|
||||
if not k.startswith('_') and k != '$schema'}
|
||||
|
||||
self.assertNotIn('$schema', filtered)
|
||||
self.assertIn('quality', filtered)
|
||||
self.assertIn('username', filtered)
|
||||
|
||||
def test_config_merging_logic(self):
|
||||
"""Test config merging logic (streamer overrides global)."""
|
||||
global_config = {
|
||||
'quality': '720p',
|
||||
'downloadVOD': True,
|
||||
'downloadCHAT': False,
|
||||
'username': 'default'
|
||||
}
|
||||
|
||||
streamer_config = {
|
||||
'username': 'specificstreamer',
|
||||
'quality': 'source', # Override
|
||||
# downloadVOD and downloadCHAT inherited from global
|
||||
}
|
||||
|
||||
# Simulate merging (same as in ConfigManager.load_streamer_config)
|
||||
merged = global_config.copy()
|
||||
merged.update(streamer_config)
|
||||
|
||||
# Check overrides
|
||||
self.assertEqual(merged['quality'], 'source') # Overridden
|
||||
self.assertEqual(merged['username'], 'specificstreamer') # Overridden
|
||||
# Check inherited values
|
||||
self.assertTrue(merged['downloadVOD'])
|
||||
self.assertFalse(merged['downloadCHAT'])
|
||||
|
||||
def test_default_config_structure(self):
|
||||
"""Test that DEFAULT_CONFIG has expected keys."""
|
||||
self.assertIn('username', DEFAULT_CONFIG)
|
||||
self.assertIn('quality', DEFAULT_CONFIG)
|
||||
self.assertIn('downloadVOD', DEFAULT_CONFIG)
|
||||
self.assertIn('downloadCHAT', DEFAULT_CONFIG)
|
||||
self.assertIn('downloadMETADATA', DEFAULT_CONFIG)
|
||||
self.assertIn('uploadCloud', DEFAULT_CONFIG)
|
||||
self.assertIn('deleteFiles', DEFAULT_CONFIG)
|
||||
self.assertIn('notifications', DEFAULT_CONFIG)
|
||||
self.assertIn('refresh', DEFAULT_CONFIG)
|
||||
|
||||
def test_enabled_flag_filtering_logic(self):
|
||||
"""Test filtering streamers by enabled flag."""
|
||||
streamers = [
|
||||
{'username': 'streamer1', 'enabled': True},
|
||||
{'username': 'streamer2', 'enabled': True},
|
||||
{'username': 'streamer3', 'enabled': False},
|
||||
{'username': 'streamer4'}, # No enabled field
|
||||
]
|
||||
|
||||
# Simulate filtering logic (same as in ConfigManager.get_all_enabled_streamers)
|
||||
enabled_streamers = [
|
||||
s['username'] for s in streamers
|
||||
if s.get('enabled', True) # Default to True if not specified
|
||||
]
|
||||
|
||||
self.assertIn('streamer1', enabled_streamers)
|
||||
self.assertIn('streamer2', enabled_streamers)
|
||||
self.assertNotIn('streamer3', enabled_streamers)
|
||||
self.assertIn('streamer4', enabled_streamers) # Default enabled
|
||||
|
||||
def test_default_streamer_config_structure(self):
|
||||
"""Test default streamer config structure."""
|
||||
# Simulate what create_default_streamer_config creates
|
||||
username = 'newstreamer'
|
||||
default_config = {
|
||||
"$schema": "../streamer.schema.json",
|
||||
"username": username,
|
||||
"enabled": True
|
||||
}
|
||||
|
||||
self.assertEqual(default_config['username'], username)
|
||||
self.assertTrue(default_config['enabled'])
|
||||
self.assertIn('$schema', default_config)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run tests with verbose output
|
||||
print("="*70)
|
||||
print("TWITCH ARCHIVE - Unit Tests for Options and Configuration")
|
||||
print("="*70)
|
||||
print()
|
||||
unittest.main(verbosity=2)
|
||||
Loading…
Add table
Add a link
Reference in a new issue