mirror of
https://github.com/rommapp/romm.git
synced 2026-02-04 04:43:21 +08:00
Create separate `tests/` folder for all tests. This will also simplify not copying tests code into the Docker image.
487 lines
20 KiB
Python
487 lines
20 KiB
Python
import os
|
|
from pathlib import Path
|
|
from unittest.mock import Mock, patch
|
|
|
|
import pytest
|
|
from config import RESOURCES_BASE_PATH
|
|
from handler.filesystem.base_handler import CoverSize
|
|
from handler.filesystem.resources_handler import FSResourcesHandler
|
|
from models.collection import Collection
|
|
from models.rom import Rom
|
|
|
|
|
|
class TestFSResourcesHandler:
|
|
"""Test suite for FSResourcesHandler class"""
|
|
|
|
@pytest.fixture
|
|
def handler(self):
|
|
return FSResourcesHandler()
|
|
|
|
@pytest.fixture
|
|
def rom(self):
|
|
"""Create a mock ROM object for testing"""
|
|
rom = Mock(spec=Rom)
|
|
rom.id = 1
|
|
rom.platform_id = 1
|
|
rom.fs_resources_path = "roms/1/1"
|
|
return rom
|
|
|
|
def test_init_uses_resources_base_path(self, handler: FSResourcesHandler):
|
|
"""Test that FSResourcesHandler initializes with RESOURCES_BASE_PATH"""
|
|
assert handler.base_path == Path(RESOURCES_BASE_PATH).resolve()
|
|
|
|
def test_get_platform_resources_path(self, handler: FSResourcesHandler):
|
|
"""Test get_platform_resources_path method"""
|
|
platform_id = 1
|
|
result = handler.get_platform_resources_path(platform_id)
|
|
expected = os.path.join("roms", "1")
|
|
assert result == expected
|
|
|
|
def test_get_platform_resources_path_different_ids(
|
|
self, handler: FSResourcesHandler
|
|
):
|
|
"""Test get_platform_resources_path with different platform IDs"""
|
|
test_ids = [1, 42, 123, 999]
|
|
for platform_id in test_ids:
|
|
result = handler.get_platform_resources_path(platform_id)
|
|
expected = os.path.join("roms", str(platform_id))
|
|
assert result == expected
|
|
|
|
def test_cover_exists_no_cover(self, handler: FSResourcesHandler, rom: Rom):
|
|
"""Test cover_exists when no cover exists"""
|
|
# Test with non-existent covers
|
|
assert not handler.cover_exists(rom, CoverSize.SMALL)
|
|
assert not handler.cover_exists(rom, CoverSize.BIG)
|
|
|
|
def test_cover_exists_with_existing_covers(self, handler: FSResourcesHandler):
|
|
"""Test cover_exists with existing covers from the test data"""
|
|
# Use existing test data structure - check directories that might exist
|
|
# Check platform 35, rom 35 (from the find output we saw earlier)
|
|
rom = Mock(spec=Rom)
|
|
rom.id = 35
|
|
rom.platform_id = 35
|
|
rom.fs_resources_path = "roms/35/35"
|
|
|
|
# These might exist based on the test data structure
|
|
small_exists = handler.cover_exists(rom, CoverSize.SMALL)
|
|
big_exists = handler.cover_exists(rom, CoverSize.BIG)
|
|
|
|
# We can't guarantee they exist, so just test the method works
|
|
assert isinstance(small_exists, bool)
|
|
assert isinstance(big_exists, bool)
|
|
|
|
def test_resize_cover_to_small_high_resolution(self, handler: FSResourcesHandler):
|
|
"""Test resize_cover_to_small with high resolution image"""
|
|
# Create a mock image with high resolution
|
|
mock_image = Mock()
|
|
mock_image.height = 1500
|
|
mock_image.width = 1000
|
|
mock_image.resize.return_value = mock_image
|
|
|
|
save_path = "/tmp/test_small.png"
|
|
|
|
handler.resize_cover_to_small(mock_image, save_path)
|
|
|
|
# Should use 0.2 ratio for high resolution
|
|
expected_width = int(1000 * 0.2)
|
|
expected_height = int(1500 * 0.2)
|
|
mock_image.resize.assert_called_once_with((expected_width, expected_height))
|
|
mock_image.save.assert_called_once_with(save_path)
|
|
|
|
def test_resize_cover_to_small_low_resolution(self, handler: FSResourcesHandler):
|
|
"""Test resize_cover_to_small with low resolution image"""
|
|
# Create a mock image with low resolution
|
|
mock_image = Mock()
|
|
mock_image.height = 800
|
|
mock_image.width = 600
|
|
mock_image.resize.return_value = mock_image
|
|
|
|
save_path = "/tmp/test_small.png"
|
|
|
|
handler.resize_cover_to_small(mock_image, save_path)
|
|
|
|
# Should use 0.4 ratio for low resolution
|
|
expected_width = int(600 * 0.4)
|
|
expected_height = int(800 * 0.4)
|
|
mock_image.resize.assert_called_once_with((expected_width, expected_height))
|
|
mock_image.save.assert_called_once_with(save_path)
|
|
|
|
def test_get_cover_path_no_cover(self, handler: FSResourcesHandler, rom: Rom):
|
|
"""Test _get_cover_path when no cover exists"""
|
|
result_small = handler._get_cover_path(rom, CoverSize.SMALL)
|
|
result_big = handler._get_cover_path(rom, CoverSize.BIG)
|
|
|
|
assert result_small is None
|
|
assert result_big is None
|
|
|
|
def test_get_cover_path_with_existing_cover(self, handler: FSResourcesHandler):
|
|
"""Test _get_cover_path with existing cover files"""
|
|
# Use test data that might exist
|
|
rom = Mock(spec=Rom)
|
|
rom.id = 27
|
|
rom.platform_id = 27
|
|
rom.fs_resources_path = "roms/27/27"
|
|
|
|
# Test both sizes
|
|
small_path = handler._get_cover_path(rom, CoverSize.SMALL)
|
|
big_path = handler._get_cover_path(rom, CoverSize.BIG)
|
|
|
|
# If paths exist, they should be relative to base path
|
|
if small_path:
|
|
assert not os.path.isabs(small_path)
|
|
assert "small" in small_path
|
|
if big_path:
|
|
assert not os.path.isabs(big_path)
|
|
assert "big" in big_path
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_cover_no_entity(self, handler: FSResourcesHandler):
|
|
"""Test get_cover with no entity"""
|
|
result = await handler.get_cover(None, False, "http://example.com/cover.png")
|
|
assert result == (None, None)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_cover_no_url(self, handler: FSResourcesHandler, rom: Rom):
|
|
"""Test get_cover with no URL"""
|
|
result = await handler.get_cover(rom, False, None)
|
|
# Should return empty strings since no covers exist and no URL provided
|
|
assert result == (None, None)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_cover_with_url_no_overwrite(
|
|
self, handler: FSResourcesHandler, rom
|
|
):
|
|
"""Test get_cover with URL but no overwrite when covers don't exist"""
|
|
url = "http://example.com/cover.png"
|
|
|
|
with patch.object(handler, "_store_cover") as mock_store:
|
|
with patch.object(handler, "cover_exists") as mock_exists:
|
|
mock_exists.return_value = False
|
|
|
|
await handler.get_cover(rom, False, url)
|
|
|
|
# Should call _store_cover for both sizes since covers don't exist
|
|
assert mock_store.call_count == 2
|
|
mock_store.assert_any_call(rom, url, CoverSize.SMALL)
|
|
mock_store.assert_any_call(rom, url, CoverSize.BIG)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_cover_with_overwrite(
|
|
self, handler: FSResourcesHandler, rom: Rom
|
|
):
|
|
"""Test get_cover with overwrite enabled"""
|
|
url = "http://example.com/cover.png"
|
|
|
|
with patch.object(handler, "_store_cover") as mock_store:
|
|
await handler.get_cover(rom, True, url)
|
|
|
|
# Should call _store_cover for both sizes regardless of existence
|
|
assert mock_store.call_count == 2
|
|
mock_store.assert_any_call(rom, url, CoverSize.SMALL)
|
|
mock_store.assert_any_call(rom, url, CoverSize.BIG)
|
|
|
|
async def test_remove_cover_no_entity(self, handler: FSResourcesHandler):
|
|
"""Test remove_cover with no entity"""
|
|
result = await handler.remove_cover(None)
|
|
assert result == {"path_cover_s": "", "path_cover_l": ""}
|
|
|
|
async def test_remove_cover_with_entity(
|
|
self, handler: FSResourcesHandler, rom: Rom
|
|
):
|
|
"""Test remove_cover with entity"""
|
|
with patch.object(handler, "remove_directory") as mock_remove:
|
|
result = await handler.remove_cover(rom)
|
|
|
|
mock_remove.assert_called_once_with(f"{rom.fs_resources_path}/cover")
|
|
assert result == {"path_cover_s": "", "path_cover_l": ""}
|
|
|
|
async def test_build_artwork_path(self, handler: FSResourcesHandler, rom: Rom):
|
|
"""Test build_artwork_path method"""
|
|
file_ext = "png"
|
|
|
|
with patch.object(handler, "make_directory") as mock_make_dir:
|
|
with patch.object(handler, "validate_path") as mock_validate:
|
|
mock_validate.side_effect = lambda x: Path(x)
|
|
|
|
path_cover_l, path_cover_s = await handler._build_artwork_path(
|
|
rom, file_ext
|
|
)
|
|
|
|
expected_cover_path = f"{rom.fs_resources_path}/cover"
|
|
expected_big_path = f"{expected_cover_path}/big.{file_ext}"
|
|
expected_small_path = f"{expected_cover_path}/small.{file_ext}"
|
|
|
|
mock_make_dir.assert_called_once_with(expected_cover_path)
|
|
assert str(path_cover_l) == expected_big_path
|
|
assert str(path_cover_s) == expected_small_path
|
|
|
|
async def test_build_artwork_path_different_extensions(
|
|
self, handler: FSResourcesHandler, rom
|
|
):
|
|
"""Test build_artwork_path with different file extensions"""
|
|
extensions = ["png", "jpg", "jpeg", "gif", "webp"]
|
|
|
|
for ext in extensions:
|
|
with patch.object(handler, "make_directory"):
|
|
with patch.object(handler, "validate_path") as mock_validate:
|
|
mock_validate.side_effect = lambda x: Path(x)
|
|
|
|
path_cover_l, path_cover_s = await handler._build_artwork_path(
|
|
rom, ext
|
|
)
|
|
|
|
# Check that the extension is properly included
|
|
assert str(path_cover_l).endswith(f"big.{ext}")
|
|
assert str(path_cover_s).endswith(f"small.{ext}")
|
|
|
|
def test_get_screenshot_path(self, handler: FSResourcesHandler, rom: Rom):
|
|
"""Test _get_screenshot_path method"""
|
|
idx = "0"
|
|
result = handler._get_screenshot_path(rom, idx)
|
|
expected = f"{rom.fs_resources_path}/screenshots/{idx}.jpg"
|
|
assert result == expected
|
|
|
|
def test_get_screenshot_path_different_indices(
|
|
self, handler: FSResourcesHandler, rom
|
|
):
|
|
"""Test _get_screenshot_path with different indices"""
|
|
indices = ["0", "1", "2", "10", "99"]
|
|
|
|
for idx in indices:
|
|
result = handler._get_screenshot_path(rom, idx)
|
|
expected = f"{rom.fs_resources_path}/screenshots/{idx}.jpg"
|
|
assert result == expected
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_rom_screenshots_no_rom(self, handler: FSResourcesHandler):
|
|
"""Test get_rom_screenshots with no ROM"""
|
|
result = await handler.get_rom_screenshots(
|
|
None, ["http://example.com/screenshot.jpg"]
|
|
)
|
|
assert result == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_rom_screenshots_no_urls(
|
|
self, handler: FSResourcesHandler, rom: Rom
|
|
):
|
|
"""Test get_rom_screenshots with no URLs"""
|
|
result = await handler.get_rom_screenshots(rom, None)
|
|
assert result == []
|
|
|
|
result = await handler.get_rom_screenshots(rom, [])
|
|
assert result == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_rom_screenshots_with_urls(
|
|
self, handler: FSResourcesHandler, rom
|
|
):
|
|
"""Test get_rom_screenshots with URLs"""
|
|
urls = [
|
|
"http://example.com/screenshot1.jpg",
|
|
"http://example.com/screenshot2.jpg",
|
|
]
|
|
|
|
with patch.object(handler, "_store_screenshot") as mock_store:
|
|
result = await handler.get_rom_screenshots(rom, urls)
|
|
|
|
# Should call _store_screenshot for each URL
|
|
assert mock_store.call_count == 2
|
|
mock_store.assert_any_call(rom, urls[0], 0)
|
|
mock_store.assert_any_call(rom, urls[1], 1)
|
|
|
|
# Should return paths for each screenshot
|
|
expected_paths = [
|
|
f"{rom.fs_resources_path}/screenshots/0.jpg",
|
|
f"{rom.fs_resources_path}/screenshots/1.jpg",
|
|
]
|
|
assert result == expected_paths
|
|
|
|
def test_manual_exists_no_manual(self, handler: FSResourcesHandler, rom: Rom):
|
|
"""Test manual_exists when no manual exists"""
|
|
assert not handler.manual_exists(rom)
|
|
|
|
def test_get_manual_path_no_manual(self, handler: FSResourcesHandler, rom: Rom):
|
|
"""Test _get_manual_path when no manual exists"""
|
|
result = handler._get_manual_path(rom)
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_manual_no_rom(self, handler: FSResourcesHandler):
|
|
"""Test get_manual with no ROM"""
|
|
result = await handler.get_manual(None, False, "http://example.com/manual.pdf")
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_manual_no_url(self, handler: FSResourcesHandler, rom: Rom):
|
|
"""Test get_manual with no URL"""
|
|
result = await handler.get_manual(rom, False, None)
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_manual_with_url_no_overwrite(
|
|
self, handler: FSResourcesHandler, rom
|
|
):
|
|
"""Test get_manual with URL but no overwrite when manual doesn't exist"""
|
|
url = "http://example.com/manual.pdf"
|
|
|
|
with patch.object(handler, "_store_manual") as mock_store:
|
|
with patch.object(handler, "manual_exists") as mock_exists:
|
|
mock_exists.return_value = False
|
|
|
|
await handler.get_manual(rom, False, url)
|
|
|
|
# Should call _store_manual since manual doesn't exist
|
|
mock_store.assert_called_once_with(rom, url)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_manual_with_overwrite(
|
|
self, handler: FSResourcesHandler, rom: Rom
|
|
):
|
|
"""Test get_manual with overwrite enabled"""
|
|
url = "http://example.com/manual.pdf"
|
|
|
|
with patch.object(handler, "_store_manual") as mock_store:
|
|
await handler.get_manual(rom, True, url)
|
|
|
|
# Should call _store_manual regardless of existence
|
|
mock_store.assert_called_once_with(rom, url)
|
|
|
|
def test_get_ra_resources_path(self, handler: FSResourcesHandler):
|
|
"""Test get_ra_resources_path method"""
|
|
platform_id = 1
|
|
rom_id = 42
|
|
|
|
result = handler.get_ra_resources_path(platform_id, rom_id)
|
|
expected = os.path.join("roms", "1", "42", "retroachievements")
|
|
assert result == expected
|
|
|
|
def test_get_ra_resources_path_different_ids(self, handler: FSResourcesHandler):
|
|
"""Test get_ra_resources_path with different IDs"""
|
|
test_cases = [(1, 1), (42, 123), (999, 456), (10, 999)]
|
|
|
|
for platform_id, rom_id in test_cases:
|
|
result = handler.get_ra_resources_path(platform_id, rom_id)
|
|
expected = os.path.join(
|
|
"roms", str(platform_id), str(rom_id), "retroachievements"
|
|
)
|
|
assert result == expected
|
|
|
|
def test_get_ra_badges_path(self, handler: FSResourcesHandler):
|
|
"""Test get_ra_badges_path method"""
|
|
platform_id = 1
|
|
rom_id = 42
|
|
|
|
result = handler.get_ra_badges_path(platform_id, rom_id)
|
|
expected = os.path.join("roms", "1", "42", "retroachievements", "badges")
|
|
assert result == expected
|
|
|
|
def test_get_ra_badges_path_different_ids(self, handler: FSResourcesHandler):
|
|
"""Test get_ra_badges_path with different IDs"""
|
|
test_cases = [(1, 1), (42, 123), (999, 456), (10, 999)]
|
|
|
|
for platform_id, rom_id in test_cases:
|
|
result = handler.get_ra_badges_path(platform_id, rom_id)
|
|
expected = os.path.join(
|
|
"roms", str(platform_id), str(rom_id), "retroachievements", "badges"
|
|
)
|
|
assert result == expected
|
|
|
|
async def test_create_ra_resources_path(self, handler: FSResourcesHandler):
|
|
"""Test create_ra_resources_path method"""
|
|
platform_id = 1
|
|
rom_id = 42
|
|
|
|
with patch.object(handler, "make_directory") as mock_make_dir:
|
|
await handler.create_ra_resources_path(platform_id, rom_id)
|
|
|
|
expected_path = handler.get_ra_resources_path(platform_id, rom_id)
|
|
mock_make_dir.assert_called_once_with(expected_path)
|
|
|
|
def test_integration_with_base_handler_methods(self, handler: FSResourcesHandler):
|
|
"""Test that FSResourcesHandler properly inherits from FSHandler"""
|
|
# Test that handler has base methods
|
|
assert hasattr(handler, "validate_path")
|
|
assert hasattr(handler, "make_directory")
|
|
assert hasattr(handler, "remove_directory")
|
|
assert hasattr(handler, "write_file_streamed")
|
|
assert hasattr(handler, "file_exists")
|
|
assert hasattr(handler, "stream_file")
|
|
|
|
def test_cover_size_enum_values(self, handler: FSResourcesHandler, rom: Rom):
|
|
"""Test that cover size enum values are handled correctly"""
|
|
# Test that the enum values are used correctly in path construction
|
|
with patch.object(handler, "validate_path") as mock_validate:
|
|
mock_validate.return_value = Path("test_path")
|
|
|
|
# Test SMALL size
|
|
handler._get_cover_path(rom, CoverSize.SMALL)
|
|
# Should look for files with "small" in the name
|
|
call_args = mock_validate.call_args[0][0]
|
|
assert "cover" in call_args
|
|
|
|
# Test BIG size
|
|
handler._get_cover_path(rom, CoverSize.BIG)
|
|
call_args = mock_validate.call_args[0][0]
|
|
assert "cover" in call_args
|
|
|
|
def test_path_construction_consistency(self, handler: FSResourcesHandler):
|
|
"""Test that path construction is consistent across methods"""
|
|
platform_id = 1
|
|
rom_id = 42
|
|
|
|
# Test platform resources path
|
|
platform_path = handler.get_platform_resources_path(platform_id)
|
|
assert platform_path.endswith(f"roms/{platform_id}")
|
|
|
|
# Test RA base path
|
|
ra_base = handler.get_ra_resources_path(platform_id, rom_id)
|
|
assert ra_base.startswith(f"roms/{platform_id}/{rom_id}")
|
|
|
|
# Test RA badges path
|
|
ra_badges = handler.get_ra_badges_path(platform_id, rom_id)
|
|
assert ra_badges.startswith(f"roms/{platform_id}/{rom_id}")
|
|
assert ra_badges.endswith("badges")
|
|
|
|
def test_entity_types_handling(self, rom, handler: FSResourcesHandler):
|
|
"""Test that both Rom and Collection entities are handled correctly"""
|
|
collection = Mock(spec=Collection)
|
|
collection.id = 1
|
|
collection.fs_resources_path = "collections/1"
|
|
|
|
# Both should work with cover_exists
|
|
rom_result = handler.cover_exists(rom, CoverSize.SMALL)
|
|
collection_result = handler.cover_exists(collection, CoverSize.SMALL)
|
|
|
|
assert isinstance(rom_result, bool)
|
|
assert isinstance(collection_result, bool)
|
|
|
|
def test_error_handling_edge_cases(self, handler: FSResourcesHandler):
|
|
"""Test error handling for edge cases"""
|
|
# Test with invalid path
|
|
invalid_entity = Mock()
|
|
invalid_entity.fs_resources_path = ""
|
|
|
|
# Should raise a ValueError
|
|
with pytest.raises(ValueError):
|
|
handler.cover_exists(invalid_entity, CoverSize.SMALL)
|
|
|
|
def test_existing_resources_structure(self, handler: FSResourcesHandler):
|
|
"""Test with the existing resources structure in romm_test"""
|
|
# Test that the handler can work with the existing directory structure
|
|
# without creating new files
|
|
|
|
# Test platform resources path with existing structure
|
|
platform_path = handler.get_platform_resources_path(35)
|
|
assert isinstance(platform_path, str)
|
|
assert "roms" in platform_path
|
|
assert "35" in platform_path
|
|
|
|
# Test RA paths with existing structure
|
|
ra_base = handler.get_ra_resources_path(35, 35)
|
|
ra_badges = handler.get_ra_badges_path(35, 35)
|
|
|
|
assert isinstance(ra_base, str)
|
|
assert isinstance(ra_badges, str)
|
|
assert "retroachievements" in ra_base
|
|
assert "badges" in ra_badges
|