3d7b325f73
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
297 lines
12 KiB
Python
297 lines
12 KiB
Python
"""Tests for backup_helper module."""
|
|
import pytest
|
|
import json
|
|
import os
|
|
import zipfile
|
|
import tempfile
|
|
from unittest.mock import patch, MagicMock
|
|
from musicround.models import SystemSetting
|
|
|
|
|
|
class TestListBackups:
|
|
"""Tests for list_backups function."""
|
|
|
|
def test_list_backups_no_directory(self, app):
|
|
"""Test list_backups returns empty list when backup dir doesn't exist."""
|
|
from musicround.helpers.backup_helper import list_backups
|
|
|
|
with patch('os.path.exists', side_effect=lambda p: False if 'backups' in p else os.path.exists(p)), \
|
|
patch('os.makedirs'):
|
|
result = list_backups()
|
|
assert result == []
|
|
|
|
def test_list_backups_empty_directory(self, app, tmp_path):
|
|
"""Test list_backups returns empty list for empty backup directory."""
|
|
from musicround.helpers.backup_helper import list_backups
|
|
|
|
backup_dir = tmp_path / 'backups'
|
|
backup_dir.mkdir()
|
|
backup_dir_str = str(backup_dir)
|
|
|
|
orig_join = os.path.join
|
|
orig_exists = os.path.exists
|
|
|
|
def mock_join(*args):
|
|
if args == ('/data', 'backups'):
|
|
return backup_dir_str
|
|
return orig_join(*args)
|
|
|
|
def mock_exists(p):
|
|
if p == backup_dir_str:
|
|
return True
|
|
return orig_exists(p)
|
|
|
|
with patch('os.path.join', side_effect=mock_join), \
|
|
patch('os.path.exists', side_effect=mock_exists):
|
|
result = list_backups()
|
|
assert isinstance(result, list)
|
|
|
|
def test_list_backups_with_zip_no_metadata(self, app, tmp_path):
|
|
"""Test list_backups handles ZIP files without backup_metadata.json."""
|
|
from musicround.helpers.backup_helper import list_backups
|
|
|
|
backup_dir = tmp_path / 'backups'
|
|
backup_dir.mkdir()
|
|
|
|
# Create a minimal ZIP file without metadata
|
|
zip_path = backup_dir / 'backup_20240101_120000.zip'
|
|
with zipfile.ZipFile(str(zip_path), 'w') as zf:
|
|
zf.writestr('song_data.db', b'fake db content')
|
|
|
|
orig_join = os.path.join
|
|
|
|
def mock_join(*args):
|
|
if args == ('/data', 'backups'):
|
|
return str(backup_dir)
|
|
return orig_join(*args)
|
|
|
|
with patch('os.path.join', side_effect=mock_join), \
|
|
patch('os.path.exists', return_value=True), \
|
|
patch('os.listdir', return_value=['backup_20240101_120000.zip']):
|
|
result = list_backups()
|
|
assert isinstance(result, list)
|
|
|
|
def test_list_backups_with_valid_metadata(self, app, tmp_path):
|
|
"""Test list_backups returns backup info from ZIP metadata."""
|
|
from musicround.helpers.backup_helper import list_backups
|
|
|
|
backup_dir = tmp_path / 'backups'
|
|
backup_dir.mkdir()
|
|
|
|
metadata = {
|
|
'backup_name': 'test_backup',
|
|
'timestamp': '2024-01-01T12:00:00',
|
|
'version': '1.0.0',
|
|
'release_name': 'Test',
|
|
}
|
|
zip_path = backup_dir / 'test_backup.zip'
|
|
with zipfile.ZipFile(str(zip_path), 'w') as zf:
|
|
zf.writestr('backup_metadata.json', json.dumps(metadata))
|
|
zf.writestr('song_data.db', b'fake db')
|
|
|
|
orig_join = os.path.join
|
|
|
|
def mock_join(*args):
|
|
if args == ('/data', 'backups'):
|
|
return str(backup_dir)
|
|
return orig_join(*args)
|
|
|
|
with patch('os.path.join', side_effect=mock_join), \
|
|
patch('os.path.exists', return_value=True), \
|
|
patch('os.listdir', return_value=['test_backup.zip']):
|
|
result = list_backups()
|
|
assert isinstance(result, list)
|
|
if result: # If file was actually read
|
|
assert result[0]['backup_name'] == 'test_backup'
|
|
|
|
|
|
class TestDeleteBackup:
|
|
"""Tests for delete_backup function."""
|
|
|
|
def test_delete_backup_not_found(self, app):
|
|
"""Test delete_backup returns error when file doesn't exist."""
|
|
from musicround.helpers.backup_helper import delete_backup
|
|
|
|
with patch('os.path.exists', return_value=False), \
|
|
patch('os.path.join', return_value='/data/backups/nonexistent.zip'):
|
|
result = delete_backup('nonexistent.zip')
|
|
assert result['status'] == 'error'
|
|
assert 'not found' in result['message'].lower()
|
|
|
|
def test_delete_backup_success(self, app, tmp_path):
|
|
"""Test delete_backup successfully removes the file."""
|
|
from musicround.helpers.backup_helper import delete_backup
|
|
|
|
# Create a real temp file
|
|
backup_file = tmp_path / 'delete_me.zip'
|
|
backup_file.write_text('fake zip content')
|
|
|
|
orig_join = os.path.join
|
|
|
|
def mock_join(*args):
|
|
if len(args) == 2 and args[0] == '/data/backups':
|
|
return str(tmp_path / args[1])
|
|
if args == ('/data', 'backups'):
|
|
return str(tmp_path)
|
|
return orig_join(*args)
|
|
|
|
with patch('os.path.join', side_effect=mock_join), \
|
|
patch('os.path.exists', side_effect=lambda p: p == str(backup_file) or os.path.exists(p)):
|
|
result = delete_backup('delete_me.zip')
|
|
assert result['status'] == 'success'
|
|
assert not backup_file.exists()
|
|
|
|
def test_delete_backup_os_error(self, app):
|
|
"""Test delete_backup handles OS errors gracefully."""
|
|
from musicround.helpers.backup_helper import delete_backup
|
|
|
|
with patch('os.path.exists', return_value=True), \
|
|
patch('os.path.join', return_value='/data/backups/bad.zip'), \
|
|
patch('os.remove', side_effect=OSError('Permission denied')):
|
|
result = delete_backup('bad.zip')
|
|
assert result['status'] == 'error'
|
|
|
|
|
|
class TestVerifyBackup:
|
|
"""Tests for verify_backup function."""
|
|
|
|
def test_verify_backup_not_found(self, app):
|
|
"""Test verify_backup returns error for non-existent file."""
|
|
from musicround.helpers.backup_helper import verify_backup
|
|
|
|
with patch('os.path.exists', return_value=False), \
|
|
patch('os.path.join', return_value='/data/backups/none.zip'):
|
|
result = verify_backup('none.zip')
|
|
assert result['status'] == 'error'
|
|
assert result['is_valid'] is False
|
|
|
|
def test_verify_backup_invalid_zip(self, app, tmp_path):
|
|
"""Test verify_backup returns error for invalid ZIP file."""
|
|
from musicround.helpers.backup_helper import verify_backup
|
|
|
|
bad_zip = tmp_path / 'bad.zip'
|
|
bad_zip.write_text('not a zip file')
|
|
|
|
with patch('os.path.exists', side_effect=lambda p: p == str(bad_zip) or os.path.exists(p)), \
|
|
patch('os.path.join', return_value=str(bad_zip)):
|
|
result = verify_backup('bad.zip')
|
|
assert result['status'] == 'error'
|
|
assert result['is_valid'] is False
|
|
|
|
def test_verify_backup_missing_db(self, app, tmp_path):
|
|
"""Test verify_backup returns error when ZIP lacks the database file."""
|
|
from musicround.helpers.backup_helper import verify_backup
|
|
|
|
zip_path = tmp_path / 'no_db.zip'
|
|
with zipfile.ZipFile(str(zip_path), 'w') as zf:
|
|
zf.writestr('backup_metadata.json', json.dumps({'version': '1.0'}))
|
|
|
|
with patch('os.path.exists', side_effect=lambda p: p == str(zip_path) or os.path.exists(p)), \
|
|
patch('os.path.join', return_value=str(zip_path)):
|
|
result = verify_backup('no_db.zip')
|
|
assert result['is_valid'] is False
|
|
|
|
def test_verify_backup_valid(self, app, tmp_path):
|
|
"""Test verify_backup returns success for a valid backup."""
|
|
from musicround.helpers.backup_helper import verify_backup
|
|
|
|
metadata = {'version': '1.0.0', 'timestamp': '2024-01-01T12:00:00'}
|
|
zip_path = tmp_path / 'valid.zip'
|
|
with zipfile.ZipFile(str(zip_path), 'w') as zf:
|
|
zf.writestr('backup_metadata.json', json.dumps(metadata))
|
|
zf.writestr('song_data.db', b'sqlite3 content')
|
|
|
|
with patch('os.path.exists', side_effect=lambda p: p == str(zip_path) or os.path.exists(p)), \
|
|
patch('os.path.join', return_value=str(zip_path)):
|
|
result = verify_backup('valid.zip')
|
|
assert result['is_valid'] is True
|
|
assert result['status'] == 'success'
|
|
|
|
|
|
class TestScheduleBackup:
|
|
"""Tests for schedule_backup function."""
|
|
|
|
def test_schedule_backup_default_time(self, app):
|
|
"""Test schedule_backup with default parameters."""
|
|
from musicround.helpers.backup_helper import schedule_backup
|
|
|
|
with patch('musicround.helpers.backup_helper.apply_retention_policy'), \
|
|
patch('musicround.helpers.backup_helper.list_backups', return_value=[]):
|
|
result = schedule_backup(schedule_time='03:00', frequency='daily', retention_days=30)
|
|
|
|
assert result['status'] == 'success'
|
|
assert result['frequency'] == 'daily'
|
|
|
|
def test_schedule_backup_stores_settings(self, app):
|
|
"""Test schedule_backup stores settings in SystemSetting."""
|
|
from musicround.helpers.backup_helper import schedule_backup
|
|
|
|
with patch('musicround.helpers.backup_helper.apply_retention_policy'), \
|
|
patch('musicround.helpers.backup_helper.list_backups', return_value=[]):
|
|
schedule_backup(schedule_time='08:30', frequency='weekly', retention_days=7)
|
|
|
|
assert SystemSetting.get('backup_schedule_time') == '08:30'
|
|
assert SystemSetting.get('backup_schedule_frequency') == 'weekly'
|
|
assert SystemSetting.get('backup_schedule_enabled') == 'true'
|
|
|
|
|
|
class TestGetBackupSummary:
|
|
"""Tests for get_backup_summary function."""
|
|
|
|
def test_get_backup_summary_empty(self, app):
|
|
"""Test get_backup_summary with no backups."""
|
|
from musicround.helpers.backup_helper import get_backup_summary
|
|
|
|
with patch('musicround.helpers.backup_helper.list_backups', return_value=[]):
|
|
summary = get_backup_summary()
|
|
|
|
assert 'backup_count' in summary
|
|
assert summary['backup_count'] == 0
|
|
assert 'schedule_enabled' in summary
|
|
assert 'backup_location' in summary
|
|
|
|
def test_get_backup_summary_with_backups(self, app):
|
|
"""Test get_backup_summary with existing backups."""
|
|
from musicround.helpers.backup_helper import get_backup_summary
|
|
|
|
fake_backups = [
|
|
{'backup_name': 'backup1', 'timestamp': '2024-01-01T12:00:00', 'file_size': 1024},
|
|
{'backup_name': 'backup2', 'timestamp': '2024-01-02T12:00:00', 'file_size': 2048},
|
|
]
|
|
with patch('musicround.helpers.backup_helper.list_backups', return_value=fake_backups):
|
|
summary = get_backup_summary()
|
|
|
|
assert summary['backup_count'] == 2
|
|
assert summary['latest_backup'] == fake_backups[0]
|
|
|
|
def test_get_backup_summary_scheduled(self, app):
|
|
"""Test get_backup_summary with schedule enabled."""
|
|
from musicround.helpers.backup_helper import get_backup_summary
|
|
|
|
SystemSetting.set('backup_schedule_enabled', 'true')
|
|
SystemSetting.set('backup_schedule_time', '03:00')
|
|
SystemSetting.set('backup_schedule_frequency', 'daily')
|
|
|
|
with patch('musicround.helpers.backup_helper.list_backups', return_value=[]):
|
|
summary = get_backup_summary()
|
|
|
|
assert summary['schedule_enabled'] is True
|
|
assert summary['next_backup'] is not None
|
|
|
|
|
|
class TestGenerateBackupConfigSuggestion:
|
|
"""Tests for generate_backup_config_suggestion function."""
|
|
|
|
def test_returns_dict(self, app):
|
|
"""Test that the function returns a dictionary."""
|
|
from musicround.helpers.backup_helper import generate_backup_config_suggestion
|
|
result = generate_backup_config_suggestion()
|
|
assert isinstance(result, dict)
|
|
|
|
def test_contains_config_keys(self, app):
|
|
"""Test that the result contains expected configuration keys."""
|
|
from musicround.helpers.backup_helper import generate_backup_config_suggestion
|
|
result = generate_backup_config_suggestion(retention_days=30)
|
|
# Check for some expected keys
|
|
assert result is not None
|