make the tests real

This commit is contained in:
Georges-Antoine Assi
2025-07-17 16:49:07 -04:00
parent 29023386a7
commit e4fdf70b63
6 changed files with 174 additions and 290 deletions

View File

@@ -1,10 +1,9 @@
import os
import shutil
import tempfile
from unittest.mock import Mock, patch
from pathlib import Path
from unittest.mock import Mock
import pytest
from handler.filesystem.assets_handler import FSAssetsHandler
from handler.filesystem.assets_handler import ASSETS_BASE_PATH, FSAssetsHandler
from models.user import User
@@ -12,40 +11,28 @@ class TestFSAssetsHandler:
"""Test suite for FSAssetsHandler class"""
@pytest.fixture
def temp_dir(self):
"""Create a temporary directory for testing"""
temp_dir = tempfile.mkdtemp()
yield temp_dir
shutil.rmtree(temp_dir, ignore_errors=True)
def handler(self):
return FSAssetsHandler()
@pytest.fixture
def handler(self, temp_dir):
"""Create FSAssetsHandler instance for testing"""
with patch("handler.filesystem.assets_handler.ASSETS_BASE_PATH", temp_dir):
return FSAssetsHandler()
def test_init_uses_assets_base_path(self, temp_dir):
def test_init_uses_assets_base_path(self, handler: FSAssetsHandler):
"""Test that FSAssetsHandler initializes with ASSETS_BASE_PATH"""
from pathlib import Path
assert handler.base_path == Path(ASSETS_BASE_PATH).resolve()
with patch("handler.filesystem.assets_handler.ASSETS_BASE_PATH", temp_dir):
handler = FSAssetsHandler()
# Use resolve() to handle macOS path resolution consistently
assert handler.base_path == Path(temp_dir).resolve()
def test_user_folder_path(self, handler, editor_user):
def test_user_folder_path(self, handler: FSAssetsHandler, editor_user: User):
"""Test user_folder_path method"""
result = handler.user_folder_path(editor_user)
expected = os.path.join("users", editor_user.fs_safe_folder_name)
assert result == expected
def test_build_avatar_path(self, handler, editor_user):
def test_build_avatar_path(self, handler: FSAssetsHandler, editor_user: User):
"""Test build_avatar_path method"""
result = handler.build_avatar_path(editor_user)
expected = os.path.join("users", editor_user.fs_safe_folder_name, "profile")
assert result == expected
def test_build_saves_file_path_without_emulator(self, handler, editor_user):
def test_build_saves_file_path_without_emulator(
self, handler: FSAssetsHandler, editor_user: User
):
"""Test build_saves_file_path method without emulator"""
platform_fs_slug = "n64"
rom_id = 456
@@ -63,7 +50,9 @@ class TestFSAssetsHandler:
)
assert result == expected
def test_build_saves_file_path_with_emulator(self, handler, editor_user):
def test_build_saves_file_path_with_emulator(
self, handler: FSAssetsHandler, editor_user: User
):
"""Test build_saves_file_path method with emulator"""
platform_fs_slug = "n64"
rom_id = 456
@@ -86,7 +75,9 @@ class TestFSAssetsHandler:
)
assert result == expected
def test_build_states_file_path_without_emulator(self, handler, editor_user):
def test_build_states_file_path_without_emulator(
self, handler: FSAssetsHandler, editor_user: User
):
"""Test build_states_file_path method without emulator"""
platform_fs_slug = "snes"
rom_id = 789
@@ -104,7 +95,9 @@ class TestFSAssetsHandler:
)
assert result == expected
def test_build_states_file_path_with_emulator(self, handler, editor_user):
def test_build_states_file_path_with_emulator(
self, handler: FSAssetsHandler, editor_user: User
):
"""Test build_states_file_path method with emulator"""
platform_fs_slug = "snes"
rom_id = 789
@@ -127,7 +120,9 @@ class TestFSAssetsHandler:
)
assert result == expected
def test_build_screenshots_file_path(self, handler, editor_user):
def test_build_screenshots_file_path(
self, handler: FSAssetsHandler, editor_user: User
):
"""Test build_screenshots_file_path method"""
platform_fs_slug = "psx"
rom_id = 101
@@ -145,7 +140,9 @@ class TestFSAssetsHandler:
)
assert result == expected
def test_build_asset_file_path_internal_method(self, handler, editor_user):
def test_build_asset_file_path_internal_method(
self, handler: FSAssetsHandler, editor_user: User
):
"""Test _build_asset_file_path internal method"""
folder = "custom_folder"
platform_fs_slug = "gba"
@@ -170,7 +167,9 @@ class TestFSAssetsHandler:
)
assert result == expected
def test_build_asset_file_path_without_emulator(self, handler, editor_user):
def test_build_asset_file_path_without_emulator(
self, handler: FSAssetsHandler, editor_user: User
):
"""Test _build_asset_file_path internal method without emulator"""
folder = "custom_folder"
platform_fs_slug = "gba"
@@ -192,7 +191,9 @@ class TestFSAssetsHandler:
)
assert result == expected
def test_integration_with_real_user_fixture(self, handler, admin_user):
def test_integration_with_real_user_fixture(
self, handler: FSAssetsHandler, admin_user: User
):
"""Test integration with real user fixture from the project"""
platform_fs_slug = "n64"
rom_id = 123
@@ -238,7 +239,9 @@ class TestFSAssetsHandler:
assert avatar_path.startswith("users/")
assert "profile" in avatar_path
def test_paths_are_relative_to_base_path(self, handler, editor_user):
def test_paths_are_relative_to_base_path(
self, handler: FSAssetsHandler, editor_user: User
):
"""Test that all generated paths are relative to the base path"""
platform_fs_slug = "ps1"
rom_id = 555
@@ -267,7 +270,7 @@ class TestFSAssetsHandler:
full_path = handler.validate_path(path)
assert full_path.is_relative_to(handler.base_path)
def test_different_users_have_different_paths(self, handler):
def test_different_users_have_different_paths(self, handler: FSAssetsHandler):
"""Test that different users get different folder paths"""
user1 = Mock(spec=User)
user1.id = 1
@@ -284,7 +287,9 @@ class TestFSAssetsHandler:
assert user1.fs_safe_folder_name in path1
assert user2.fs_safe_folder_name in path2
def test_rom_id_conversion_to_string(self, handler, editor_user):
def test_rom_id_conversion_to_string(
self, handler: FSAssetsHandler, editor_user: User
):
"""Test that rom_id is properly converted to string in paths"""
platform_fs_slug = "dc"
rom_id = 12345
@@ -296,7 +301,9 @@ class TestFSAssetsHandler:
assert str(rom_id) in saves_path
assert saves_path.endswith(str(rom_id))
def test_emulator_parameter_handling(self, handler, editor_user):
def test_emulator_parameter_handling(
self, handler: FSAssetsHandler, editor_user: User
):
"""Test that emulator parameter is handled consistently"""
platform_fs_slug = "ps2"
rom_id = 777

View File

@@ -1,35 +1,26 @@
import binascii
import hashlib
import shutil
import tempfile
from unittest.mock import Mock, patch
from pathlib import Path
from unittest.mock import patch
import pytest
from config.config_manager import Config
from exceptions.fs_exceptions import FirmwareAlreadyExistsException
from config.config_manager import LIBRARY_BASE_PATH, Config
from handler.filesystem.firmware_handler import FSFirmwareHandler
from utils.hashing import crc32_to_hex
class TestFSFirmwareHandler:
"""Test suite for FSFirmwareHandler class"""
@pytest.fixture
def temp_dir(self):
"""Create a temporary directory for testing"""
temp_dir = tempfile.mkdtemp()
yield temp_dir
shutil.rmtree(temp_dir, ignore_errors=True)
@pytest.fixture
def handler(self, temp_dir):
"""Create FSFirmwareHandler instance for testing"""
with patch("handler.filesystem.firmware_handler.LIBRARY_BASE_PATH", temp_dir):
return FSFirmwareHandler()
def handler(self):
return FSFirmwareHandler()
@pytest.fixture
def config(self):
return Config(
EXCLUDED_PLATFORMS=[],
EXCLUDED_SINGLE_EXT=[],
EXCLUDED_SINGLE_EXT=["tmp"],
EXCLUDED_SINGLE_FILES=[],
EXCLUDED_MULTI_FILES=[],
EXCLUDED_MULTI_PARTS_EXT=[],
@@ -40,196 +31,99 @@ class TestFSFirmwareHandler:
FIRMWARE_FOLDER_NAME="bios",
)
@pytest.fixture
def sample_firmware_content(self):
"""Sample firmware content for testing"""
return b"This is test firmware content for hashing and file operations"
def test_init_uses_library_base_path(self, temp_dir):
def test_init_uses_library_base_path(self, handler: FSFirmwareHandler):
"""Test that FSFirmwareHandler initializes with LIBRARY_BASE_PATH"""
from pathlib import Path
assert handler.base_path == Path(LIBRARY_BASE_PATH).resolve()
with patch("handler.filesystem.firmware_handler.LIBRARY_BASE_PATH", temp_dir):
handler = FSFirmwareHandler()
assert handler.base_path == Path(temp_dir).resolve()
def test_get_firmware_fs_structure_high_prio_exists(self, handler, config):
"""Test get_firmware_fs_structure when high priority structure exists"""
fs_slug = "ps2"
with patch(
"handler.filesystem.firmware_handler.cm.get_config", return_value=config
):
with patch("os.path.exists", return_value=True):
result = handler.get_firmware_fs_structure(fs_slug)
expected = f"{config.FIRMWARE_FOLDER_NAME}/{fs_slug}"
assert result == expected
def test_get_firmware_fs_structure_high_prio_not_exists(self, handler, config):
"""Test get_firmware_fs_structure when high priority structure doesn't exist"""
fs_slug = "ps2"
with patch(
"handler.filesystem.firmware_handler.cm.get_config", return_value=config
):
with patch("os.path.exists", return_value=False):
result = handler.get_firmware_fs_structure(fs_slug)
expected = f"{fs_slug}/{config.FIRMWARE_FOLDER_NAME}"
assert result == expected
def test_get_firmware_success(self, handler, config):
"""Test get_firmware method with successful file listing"""
def test_get_firmware(self, handler, config):
"""Test get_firmware method"""
platform_fs_slug = "n64"
firmware_files = ["bios1.bin", "bios2.bin", "excluded.tmp"]
filtered_files = ["bios1.bin", "bios2.bin"]
with patch(
"handler.filesystem.firmware_handler.cm.get_config", return_value=config
):
with patch("os.path.exists", return_value=True):
with patch.object(handler, "list_files", return_value=firmware_files):
with patch.object(
handler, "exclude_single_files", return_value=filtered_files
):
result = handler.get_firmware(platform_fs_slug)
assert result == filtered_files
result = handler.get_firmware(platform_fs_slug)
assert "bios1.bin" in result
assert "bios2.bin" in result
assert "temp.tmp" not in result
def test_get_firmware_calls_correct_methods(self, handler, config):
"""Test that get_firmware calls the correct methods in sequence"""
platform_fs_slug = "psx"
firmware_files = ["bios.bin"]
with patch(
"handler.filesystem.firmware_handler.cm.get_config", return_value=config
):
with patch("os.path.exists", return_value=True):
with patch.object(
handler, "list_files", return_value=firmware_files
) as mock_list:
with patch.object(
handler, "exclude_single_files", return_value=firmware_files
) as mock_exclude:
result = handler.get_firmware(platform_fs_slug)
result = handler.get_firmware(platform_fs_slug)
assert "scph1001.bin" in result
# Verify the correct path was used
expected_path = (
f"{config.FIRMWARE_FOLDER_NAME}/{platform_fs_slug}"
)
mock_list.assert_called_once_with(path=expected_path)
mock_exclude.assert_called_once_with(firmware_files)
assert result == firmware_files
def test_get_firmware_nonexistent_platform(self, handler, config):
"""Test get_firmware method with nonexistent platform"""
platform_fs_slug = "nonexistent"
def test_calculate_file_hashes(self, handler, sample_firmware_content):
"""Test calculate_file_hashes method"""
firmware_path = "test_platform/bios"
file_name = "test_bios.bin"
with patch(
"handler.filesystem.firmware_handler.cm.get_config", return_value=config
):
with pytest.raises(FileNotFoundError):
handler.get_firmware(platform_fs_slug)
# Create a mock file stream that supports context manager protocol
mock_stream = Mock()
mock_stream.read.side_effect = [
sample_firmware_content,
b"",
] # First read returns content, second returns empty
mock_stream.__enter__ = Mock(return_value=mock_stream)
mock_stream.__exit__ = Mock(return_value=None)
def test_calculate_file_hashes(self, handler: FSFirmwareHandler):
"""Test calculate_file_hashes method with actual file"""
firmware_path = "n64/bios"
file_name = "bios1.bin"
with patch.object(handler, "stream_file", return_value=mock_stream):
result = handler.calculate_file_hashes(firmware_path, file_name)
result = handler.calculate_file_hashes(firmware_path, file_name)
# Verify the structure of the result
assert isinstance(result, dict)
assert "crc_hash" in result
assert "md5_hash" in result
assert "sha1_hash" in result
assert isinstance(result, dict)
assert "crc_hash" in result
assert "md5_hash" in result
assert "sha1_hash" in result
# Verify hash values are strings
assert isinstance(result["crc_hash"], str)
assert isinstance(result["md5_hash"], str)
assert isinstance(result["sha1_hash"], str)
assert isinstance(result["crc_hash"], str)
assert isinstance(result["md5_hash"], str)
assert isinstance(result["sha1_hash"], str)
def test_calculate_file_hashes_actual_values(
self, handler, sample_firmware_content
):
"""Test calculate_file_hashes with actual hash verification"""
firmware_path = "test_platform/bios"
file_name = "test_bios.bin"
assert len(result["crc_hash"]) > 0
assert len(result["md5_hash"]) > 0
assert len(result["sha1_hash"]) > 0
# Calculate expected values
import binascii
file_path = handler.base_path / firmware_path / file_name
with open(file_path, "rb") as f:
content = f.read()
from utils.hashing import crc32_to_hex
crc_expected = crc32_to_hex(binascii.crc32(content))
md5_expected = hashlib.md5(content, usedforsecurity=False).hexdigest()
sha1_expected = hashlib.sha1(content, usedforsecurity=False).hexdigest()
crc_expected = crc32_to_hex(binascii.crc32(sample_firmware_content))
md5_expected = hashlib.md5(
sample_firmware_content, usedforsecurity=False
).hexdigest()
sha1_expected = hashlib.sha1(
sample_firmware_content, usedforsecurity=False
).hexdigest()
result = handler.calculate_file_hashes(firmware_path, file_name)
# Create a mock file stream that supports context manager protocol
mock_stream = Mock()
mock_stream.read.side_effect = [sample_firmware_content, b""]
mock_stream.__enter__ = Mock(return_value=mock_stream)
mock_stream.__exit__ = Mock(return_value=None)
assert result["crc_hash"] == crc_expected
assert result["md5_hash"] == md5_expected
assert result["sha1_hash"] == sha1_expected
with patch.object(handler, "stream_file", return_value=mock_stream):
result = handler.calculate_file_hashes(firmware_path, file_name)
def test_calculate_file_hashes_different_files(self, handler: FSFirmwareHandler):
"""Test calculate_file_hashes with different files have different hashes"""
firmware_path = "n64/bios"
assert result["crc_hash"] == crc_expected
assert result["md5_hash"] == md5_expected
assert result["sha1_hash"] == sha1_expected
result1 = handler.calculate_file_hashes(firmware_path, "bios1.bin")
result2 = handler.calculate_file_hashes(firmware_path, "bios2.bin")
def test_calculate_file_hashes_chunked_reading(self, handler):
"""Test calculate_file_hashes with chunked reading"""
firmware_path = "test_platform/bios"
file_name = "test_bios.bin"
# Different files should have different hashes
assert result1["crc_hash"] != result2["crc_hash"]
assert result1["md5_hash"] != result2["md5_hash"]
assert result1["sha1_hash"] != result2["sha1_hash"]
# Create test data larger than chunk size
chunk1 = b"A" * 4096
chunk2 = b"B" * 4096
chunk3 = b"C" * 1000
def test_calculate_file_hashes_nonexistent_file(self, handler: FSFirmwareHandler):
"""Test calculate_file_hashes with nonexistent file"""
firmware_path = "n64/bios"
file_name = "nonexistent.bin"
# Create a mock file stream that returns chunks and supports context manager protocol
mock_stream = Mock()
mock_stream.read.side_effect = [chunk1, chunk2, chunk3, b""]
mock_stream.__enter__ = Mock(return_value=mock_stream)
mock_stream.__exit__ = Mock(return_value=None)
with patch.object(handler, "stream_file", return_value=mock_stream):
result = handler.calculate_file_hashes(firmware_path, file_name)
# Verify that multiple reads were made
assert mock_stream.read.call_count == 4
# Verify results are still strings
assert isinstance(result["crc_hash"], str)
assert isinstance(result["md5_hash"], str)
assert isinstance(result["sha1_hash"], str)
def test_calculate_file_hashes_stream_file_called_correctly(self, handler):
"""Test that calculate_file_hashes calls stream_file with correct parameters"""
firmware_path = "test_platform/bios"
file_name = "test_bios.bin"
mock_stream = Mock()
mock_stream.read.side_effect = [b"test", b""]
mock_stream.__enter__ = Mock(return_value=mock_stream)
mock_stream.__exit__ = Mock(return_value=None)
with patch.object(
handler, "stream_file", return_value=mock_stream
) as mock_stream_method:
with pytest.raises(FileNotFoundError):
handler.calculate_file_hashes(firmware_path, file_name)
expected_file_path = f"{firmware_path}/{file_name}"
mock_stream_method.assert_called_once_with(file_path=expected_file_path)
def test_rename_file_same_name(self, handler):
def test_rename_file_same_name(self, handler: FSFirmwareHandler):
"""Test rename_file when old and new names are the same"""
old_name = "bios.bin"
new_name = "bios.bin"
file_path = "ps2/bios"
old_name = "bios1.bin"
new_name = "bios1.bin"
file_path = "n64/bios"
# Should not call any file operations
with patch.object(handler, "file_exists") as mock_exists:
@@ -239,52 +133,7 @@ class TestFSFirmwareHandler:
mock_exists.assert_not_called()
mock_move.assert_not_called()
def test_rename_file_different_name_success(self, handler):
"""Test rename_file when names are different and target doesn't exist"""
old_name = "old_bios.bin"
new_name = "new_bios.bin"
file_path = "ps2/bios"
with patch.object(handler, "file_exists", return_value=False):
with patch.object(handler, "move_file") as mock_move:
handler.rename_file(old_name, new_name, file_path)
# The method reassigns file_path to include new_name
modified_file_path = f"{file_path}/{new_name}"
expected_source = f"{modified_file_path}/{old_name}"
expected_dest = f"{modified_file_path}/{new_name}"
mock_move.assert_called_once_with(
source_path=expected_source, dest_path=expected_dest
)
def test_rename_file_target_exists_raises_exception(self, handler):
"""Test rename_file raises exception when target file already exists"""
old_name = "old_bios.bin"
new_name = "existing_bios.bin"
file_path = "ps2/bios"
with patch.object(handler, "file_exists", return_value=True):
with patch.object(handler, "move_file") as mock_move:
with pytest.raises(FirmwareAlreadyExistsException):
handler.rename_file(old_name, new_name, file_path)
mock_move.assert_not_called()
def test_rename_file_file_exists_called_correctly(self, handler):
"""Test that rename_file calls file_exists with correct path"""
old_name = "old_bios.bin"
new_name = "new_bios.bin"
file_path = "ps2/bios"
with patch.object(handler, "file_exists", return_value=False) as mock_exists:
with patch.object(handler, "move_file"):
handler.rename_file(old_name, new_name, file_path)
# The method reassigns file_path to include new_name before checking
modified_file_path = f"{file_path}/{new_name}"
mock_exists.assert_called_once_with(file_path=modified_file_path)
def test_integration_with_base_handler_methods(self, handler):
def test_integration_with_base_handler_methods(self, handler: FSFirmwareHandler):
"""Test that FSFirmwareHandler properly inherits from FSHandler"""
# Test that handler has base methods
assert hasattr(handler, "validate_path")
@@ -294,48 +143,72 @@ class TestFSFirmwareHandler:
assert hasattr(handler, "stream_file")
assert hasattr(handler, "exclude_single_files")
def test_firmware_path_construction(self, handler, config):
def test_firmware_path_construction(self, handler: FSFirmwareHandler, config):
"""Test that firmware paths are constructed correctly"""
platform_fs_slug = "dreamcast"
platform_fs_slug = "n64"
with patch(
"handler.filesystem.firmware_handler.cm.get_config", return_value=config
):
# Test high priority path
with patch("os.path.exists", return_value=True):
path = handler.get_firmware_fs_structure(platform_fs_slug)
assert path == f"{config.FIRMWARE_FOLDER_NAME}/{platform_fs_slug}"
# Test normal path
with patch("os.path.exists", return_value=False):
path = handler.get_firmware_fs_structure(platform_fs_slug)
assert path == f"{platform_fs_slug}/{config.FIRMWARE_FOLDER_NAME}"
def test_error_handling_in_get_firmware(self, handler, config):
"""Test error handling in get_firmware method"""
platform_fs_slug = "invalid_platform"
with patch(
"handler.filesystem.firmware_handler.cm.get_config", return_value=config
):
with patch("os.path.exists", return_value=True):
with patch.object(
handler,
"list_files",
side_effect=FileNotFoundError("Directory not found"),
):
with pytest.raises(FileNotFoundError):
handler.get_firmware(platform_fs_slug)
# Test normal path (high prio doesn't exist)
path = handler.get_firmware_fs_structure(platform_fs_slug)
assert path == f"{platform_fs_slug}/{config.FIRMWARE_FOLDER_NAME}"
def test_multiple_platform_handling(self, handler, config):
"""Test handling of different platform slugs"""
platforms = ["ps1", "ps2", "n64", "dreamcast", "saturn"]
platforms = ["n64", "psx"] # Use platforms that actually exist in test data
with patch(
"handler.filesystem.firmware_handler.cm.get_config", return_value=config
):
with patch("os.path.exists", return_value=True):
for platform in platforms:
path = handler.get_firmware_fs_structure(platform)
assert platform in path
assert config.FIRMWARE_FOLDER_NAME in path
for platform in platforms:
path = handler.get_firmware_fs_structure(platform)
assert platform in path
assert config.FIRMWARE_FOLDER_NAME in path
# Test that we can actually get firmware for existing platforms
firmware_files = handler.get_firmware(platform)
assert len(firmware_files) > 0 # Should have at least one file
def test_exclude_single_files_integration(self, handler, config):
"""Test that exclude_single_files works with actual files"""
platform_fs_slug = "n64"
with patch(
"handler.filesystem.firmware_handler.cm.get_config", return_value=config
):
# Get all files in the directory
firmware_path = handler.get_firmware_fs_structure(platform_fs_slug)
all_files = handler.list_files(path=firmware_path)
# Should include .tmp files before exclusion
assert "temp.tmp" in all_files
assert "bios1.bin" in all_files
assert "bios2.bin" in all_files
# After exclusion, .tmp files should be removed
filtered_files = handler.exclude_single_files(all_files)
assert "temp.tmp" not in filtered_files
assert "bios1.bin" in filtered_files
assert "bios2.bin" in filtered_files
def test_file_operations_with_actual_structure(self, handler: FSFirmwareHandler):
"""Test that file operations work with the actual directory structure"""
# Test that we can list files
n64_files = handler.list_files("n64/bios")
assert len(n64_files) > 0
psx_files = handler.list_files("psx/bios")
assert len(psx_files) > 0
# Test that we can check file existence
assert handler.file_exists("n64/bios/bios1.bin")
assert handler.file_exists("psx/bios/scph1001.bin")
assert not handler.file_exists("n64/bios/nonexistent.bin")
def test_stream_file_with_actual_files(self, handler: FSFirmwareHandler):
"""Test streaming actual files"""
with handler.stream_file("n64/bios/bios1.bin") as f:
content = f.read()
assert len(content) > 0
assert b"This is a test N64 BIOS file 1" in content

View File

@@ -0,0 +1 @@
This is a test N64 BIOS file 1

View File

@@ -0,0 +1 @@
This is a test N64 BIOS file 2

View File

@@ -0,0 +1 @@
This is a test temporary file

View File

@@ -0,0 +1 @@
This is a test PSX BIOS file