mirror of
https://github.com/rommapp/romm.git
synced 2026-06-27 22:35:57 +00:00
Merge branch 'master' into copilot/fix-m3u-disc-selection-issue
This commit is contained in:
@@ -374,13 +374,25 @@ class FSRomsHandler(FSHandler):
|
||||
|
||||
def exclude_multi_roms(self, roms: list[str]) -> list[str]:
|
||||
excluded_names = cm.get_config().EXCLUDED_MULTI_FILES
|
||||
filtered_files: list = []
|
||||
normalized_patterns = [
|
||||
excluded_name.lower().strip() for excluded_name in excluded_names
|
||||
]
|
||||
|
||||
kept_roms: list[str] = []
|
||||
for rom in roms:
|
||||
if rom in excluded_names:
|
||||
filtered_files.append(rom)
|
||||
normalized_rom_name = rom.strip().lower()
|
||||
if normalized_rom_name in normalized_patterns:
|
||||
continue
|
||||
|
||||
return [f for f in roms if f not in filtered_files]
|
||||
if any(
|
||||
fnmatch.fnmatch(normalized_rom_name, pattern)
|
||||
for pattern in normalized_patterns
|
||||
):
|
||||
continue
|
||||
|
||||
kept_roms.append(rom)
|
||||
|
||||
return kept_roms
|
||||
|
||||
def _iter_m3u_referenced_paths(self, abs_fs_path: Path, m3u_file_name: str) -> Iterator[Path]:
|
||||
m3u_path = Path(abs_fs_path, m3u_file_name)
|
||||
|
||||
@@ -462,6 +462,7 @@ class IGDBHandler(MetadataHandler):
|
||||
GameType.PORT,
|
||||
GameType.REMAKE,
|
||||
GameType.REMASTER,
|
||||
GameType.STANDALONE_EXPANSION,
|
||||
)
|
||||
game_type_filter = f"& game_type=({','.join(map(str, categories))})"
|
||||
else:
|
||||
@@ -509,14 +510,26 @@ class IGDBHandler(MetadataHandler):
|
||||
limit=self.pagination_limit,
|
||||
)
|
||||
|
||||
if roms_expanded:
|
||||
log.debug(
|
||||
"Searching expanded in games endpoint for expanded game %s",
|
||||
roms_expanded[0]["game"],
|
||||
# Collect all unique game IDs from the expanded search results,
|
||||
# skipping entries without a valid game id.
|
||||
unique_game_ids = list(
|
||||
dict.fromkeys(
|
||||
game_id
|
||||
for r in roms_expanded
|
||||
if (g := r.get("game")) and (game_id := g.get("id")) is not None
|
||||
)
|
||||
)
|
||||
|
||||
if unique_game_ids:
|
||||
log.debug(
|
||||
"Searching expanded in games endpoint for %d candidate game(s): %s",
|
||||
len(unique_game_ids),
|
||||
unique_game_ids,
|
||||
)
|
||||
id_filter = " | ".join(f"id={gid}" for gid in unique_game_ids)
|
||||
extra_roms = await self.igdb_service.list_games(
|
||||
fields=GAMES_FIELDS,
|
||||
where=f"id={roms_expanded[0]['game']['id']}",
|
||||
where=f"({id_filter})",
|
||||
limit=self.pagination_limit,
|
||||
)
|
||||
|
||||
|
||||
@@ -412,7 +412,7 @@ async def scan_rom(
|
||||
or (scan_type == ScanType.UPDATE and rom.hasheous_id)
|
||||
or (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and not rom.hasheous_id
|
||||
and (not rom.hasheous_id or not rom.hasheous_metadata)
|
||||
and rom.platform_slug in HASHEOUS_PLATFORM_LIST
|
||||
)
|
||||
)
|
||||
@@ -463,7 +463,7 @@ async def scan_rom(
|
||||
or (scan_type == ScanType.UPDATE and rom.igdb_id)
|
||||
or (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and not rom.igdb_id
|
||||
and (not rom.igdb_id or not rom.igdb_metadata)
|
||||
and rom.platform_slug in IGDB_PLATFORM_LIST
|
||||
)
|
||||
)
|
||||
@@ -509,7 +509,10 @@ async def scan_rom(
|
||||
newly_added
|
||||
or scan_type == ScanType.COMPLETE
|
||||
or (scan_type == ScanType.UPDATE and rom.gamelist_id)
|
||||
or (scan_type == ScanType.UNMATCHED and not rom.gamelist_id)
|
||||
or (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and (not rom.gamelist_id or not rom.gamelist_metadata)
|
||||
)
|
||||
):
|
||||
return await meta_gamelist_handler.get_rom(
|
||||
rom_attrs["fs_name"], platform, rom
|
||||
@@ -527,12 +530,16 @@ async def scan_rom(
|
||||
or (scan_type == ScanType.UPDATE and rom.flashpoint_id)
|
||||
or (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and not rom.flashpoint_id
|
||||
and (not rom.flashpoint_id or not rom.flashpoint_metadata)
|
||||
and platform.slug in FLASHPOINT_PLATFORM_LIST
|
||||
)
|
||||
)
|
||||
):
|
||||
if scan_type == ScanType.UPDATE and rom.flashpoint_id:
|
||||
if (scan_type == ScanType.UPDATE and rom.flashpoint_id) or (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and rom.flashpoint_id
|
||||
and not rom.flashpoint_metadata
|
||||
):
|
||||
return await meta_flashpoint_handler.get_rom_by_id(rom.flashpoint_id)
|
||||
else:
|
||||
return await meta_flashpoint_handler.get_rom(
|
||||
@@ -566,7 +573,10 @@ async def scan_rom(
|
||||
newly_added
|
||||
or scan_type == ScanType.COMPLETE
|
||||
or (scan_type == ScanType.UPDATE and rom.hltb_id)
|
||||
or (scan_type == ScanType.UNMATCHED and not rom.hltb_id)
|
||||
or (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and (not rom.hltb_id or not rom.hltb_metadata)
|
||||
)
|
||||
)
|
||||
):
|
||||
return await meta_hltb_handler.get_rom(rom_attrs["fs_name"], platform.slug)
|
||||
@@ -583,7 +593,7 @@ async def scan_rom(
|
||||
or (scan_type == ScanType.UPDATE and rom.moby_id)
|
||||
or (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and not rom.moby_id
|
||||
and (not rom.moby_id or not rom.moby_metadata)
|
||||
and rom.platform_slug in MOBYGAMES_PLATFORM_LIST
|
||||
)
|
||||
)
|
||||
@@ -615,7 +625,7 @@ async def scan_rom(
|
||||
or (scan_type == ScanType.UPDATE and rom.ss_id)
|
||||
or (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and not rom.ss_id
|
||||
and (not rom.ss_id or not rom.ss_metadata)
|
||||
and rom.platform_slug in SCREENSAVER_PLATFORM_LIST
|
||||
)
|
||||
)
|
||||
@@ -656,7 +666,7 @@ async def scan_rom(
|
||||
or (scan_type == ScanType.UPDATE and rom.launchbox_id)
|
||||
or (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and not rom.launchbox_id
|
||||
and (not rom.launchbox_id or not rom.launchbox_metadata)
|
||||
and rom.platform_slug in LAUNCHBOX_PLATFORM_LIST
|
||||
)
|
||||
):
|
||||
@@ -671,6 +681,19 @@ async def scan_rom(
|
||||
fs_name=rom_attrs["fs_name"],
|
||||
platform_slug=platform_slug,
|
||||
)
|
||||
elif (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and rom.launchbox_id
|
||||
and not rom.launchbox_metadata
|
||||
and launchbox_remote_enabled
|
||||
):
|
||||
# ID was set manually but metadata was never fetched
|
||||
launchbox_rom = await meta_launchbox_handler.get_rom_by_id(
|
||||
rom.launchbox_id,
|
||||
remote_enabled=True,
|
||||
fs_name=rom_attrs["fs_name"],
|
||||
platform_slug=platform_slug,
|
||||
)
|
||||
elif playmatch_rom["launchbox_id"] is not None and launchbox_remote_enabled:
|
||||
log.debug(
|
||||
f"{hl(rom_attrs['fs_name'])} identified by Playmatch as LaunchBox "
|
||||
@@ -709,7 +732,7 @@ async def scan_rom(
|
||||
or (scan_type == ScanType.UPDATE and rom.ra_id)
|
||||
or (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and not rom.ra_id
|
||||
and (not rom.ra_id or not rom.ra_metadata)
|
||||
and rom.platform_slug in RA_PLATFORM_LIST
|
||||
)
|
||||
)
|
||||
@@ -724,7 +747,9 @@ async def scan_rom(
|
||||
)
|
||||
return await meta_ra_handler.get_rom_by_id(rom=rom, ra_id=h_ra_id)
|
||||
|
||||
if scan_type == ScanType.UPDATE and rom.ra_id:
|
||||
if (scan_type == ScanType.UPDATE and rom.ra_id) or (
|
||||
scan_type == ScanType.UNMATCHED and rom.ra_id and not rom.ra_metadata
|
||||
):
|
||||
return await meta_ra_handler.get_rom_by_id(rom=rom, ra_id=rom.ra_id)
|
||||
else:
|
||||
return await meta_ra_handler.get_rom(
|
||||
@@ -743,7 +768,7 @@ async def scan_rom(
|
||||
or (scan_type == ScanType.UPDATE and rom.hasheous_id)
|
||||
or (
|
||||
scan_type == ScanType.UNMATCHED
|
||||
and not rom.hasheous_id
|
||||
and (not rom.hasheous_id or not rom.hasheous_metadata)
|
||||
and rom.platform_slug in HASHEOUS_PLATFORM_LIST
|
||||
)
|
||||
)
|
||||
|
||||
@@ -249,6 +249,41 @@ class TestFSRomsHandler:
|
||||
result = handler.exclude_multi_roms(roms)
|
||||
assert result == roms
|
||||
|
||||
def test_exclude_multi_roms_case_insensitive(self, handler: FSRomsHandler, config):
|
||||
"""Test exclude_multi_roms ignores case in excluded names"""
|
||||
roms = ["Game1", "Manuals", "Game2"]
|
||||
config.EXCLUDED_MULTI_FILES = ["manuals"]
|
||||
|
||||
with pytest.MonkeyPatch.context() as m:
|
||||
m.setattr("handler.filesystem.roms_handler.cm.get_config", lambda: config)
|
||||
|
||||
result = handler.exclude_multi_roms(roms)
|
||||
assert result == ["Game1", "Game2"]
|
||||
|
||||
def test_exclude_multi_roms_ignores_whitespace(
|
||||
self, handler: FSRomsHandler, config
|
||||
):
|
||||
"""Test exclude_multi_roms trims accidental surrounding whitespace"""
|
||||
roms = ["Game1", "covers", "Game2"]
|
||||
config.EXCLUDED_MULTI_FILES = [" covers "]
|
||||
|
||||
with pytest.MonkeyPatch.context() as m:
|
||||
m.setattr("handler.filesystem.roms_handler.cm.get_config", lambda: config)
|
||||
|
||||
result = handler.exclude_multi_roms(roms)
|
||||
assert result == ["Game1", "Game2"]
|
||||
|
||||
def test_exclude_multi_roms_wildcard_patterns(self, handler: FSRomsHandler, config):
|
||||
"""Test exclude_multi_roms keeps wildcard matching with normalized config"""
|
||||
roms = ["Game1", "Manuals", "manuals-fr", "Game2"]
|
||||
config.EXCLUDED_MULTI_FILES = [" manuals* "]
|
||||
|
||||
with pytest.MonkeyPatch.context() as m:
|
||||
m.setattr("handler.filesystem.roms_handler.cm.get_config", lambda: config)
|
||||
|
||||
result = handler.exclude_multi_roms(roms)
|
||||
assert result == ["Game1", "Game2"]
|
||||
|
||||
def test_build_rom_file_single_file(self, rom_single: Rom, handler: FSRomsHandler):
|
||||
"""Test _build_rom_file with actual single ROM file"""
|
||||
rom_path = Path(rom_single.fs_path)
|
||||
|
||||
157
backend/tests/handler/metadata/test_igdb_handler.py
Normal file
157
backend/tests/handler/metadata/test_igdb_handler.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""Tests for the IGDB metadata handler."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from adapters.services.igdb_types import GameType
|
||||
from handler.metadata.igdb_handler import IGDBHandler
|
||||
|
||||
GENESIS_IGDB_ID = 29
|
||||
|
||||
|
||||
def _make_game(game_id: int, name: str) -> dict:
|
||||
"""Build a minimal IGDB Game dict for testing."""
|
||||
return {
|
||||
"id": game_id,
|
||||
"name": name,
|
||||
"slug": name.lower().replace(" ", "-"),
|
||||
"summary": "",
|
||||
"total_rating": 0.0,
|
||||
"aggregated_rating": 0.0,
|
||||
"first_release_date": None,
|
||||
"artworks": [],
|
||||
"cover": None,
|
||||
"screenshots": [],
|
||||
"platforms": [{"id": GENESIS_IGDB_ID, "name": "Sega Mega Drive/Genesis"}],
|
||||
"alternative_names": [],
|
||||
"genres": [],
|
||||
"franchise": None,
|
||||
"franchises": [],
|
||||
"collections": [],
|
||||
"game_modes": [],
|
||||
"involved_companies": [],
|
||||
"expansions": [],
|
||||
"dlcs": [],
|
||||
"remasters": [],
|
||||
"remakes": [],
|
||||
"expanded_games": [],
|
||||
"ports": [],
|
||||
"similar_games": [],
|
||||
"videos": [],
|
||||
"age_ratings": [],
|
||||
"multiplayer_modes": [],
|
||||
"game_localizations": [],
|
||||
}
|
||||
|
||||
|
||||
class TestSearchRomGameTypeFilter:
|
||||
"""Tests for _search_rom game_type filtering."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_standalone_expansion_included_in_game_type_filter(self):
|
||||
"""Searching with game_type filter must include STANDALONE_EXPANSION
|
||||
so that games like 'Ecco: The Tides of Time' are found on the first
|
||||
search pass and not confused with their parent game."""
|
||||
handler = IGDBHandler()
|
||||
|
||||
ecco_dolphin = _make_game(1799, "Ecco the Dolphin")
|
||||
ecco_tides = _make_game(5379, "Ecco: The Tides of Time")
|
||||
|
||||
async def mock_list_games(
|
||||
search_term=None, fields=None, where=None, limit=None
|
||||
):
|
||||
# First call (with game_type filter): return both games
|
||||
if where and "game_type" in where:
|
||||
# Verify STANDALONE_EXPANSION (4) is in the filter
|
||||
assert (
|
||||
str(int(GameType.STANDALONE_EXPANSION)) in where
|
||||
), f"STANDALONE_EXPANSION should be in game_type filter, got: {where}"
|
||||
# Simulate IGDB returning both games when the search includes
|
||||
# standalone expansions
|
||||
if search_term and "tides of time" in search_term.lower():
|
||||
return [ecco_dolphin, ecco_tides]
|
||||
return [ecco_dolphin]
|
||||
return []
|
||||
|
||||
with (
|
||||
patch(
|
||||
"handler.metadata.igdb_handler.IGDBHandler.is_enabled",
|
||||
return_value=True,
|
||||
),
|
||||
patch.object(
|
||||
handler.igdb_service,
|
||||
"list_games",
|
||||
side_effect=mock_list_games,
|
||||
),
|
||||
patch.object(
|
||||
handler.igdb_service,
|
||||
"search",
|
||||
new_callable=AsyncMock,
|
||||
return_value=[],
|
||||
),
|
||||
):
|
||||
result = await handler._search_rom(
|
||||
"ecco the tides of time", GENESIS_IGDB_ID, with_game_type=True
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert (
|
||||
result["id"] == 5379
|
||||
), f"Expected Ecco: The Tides of Time (id=5379), got {result.get('name')} (id={result.get('id')})"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_expanded_search_uses_all_results_not_just_first(self):
|
||||
"""When the primary search fails and the expanded IGDB search endpoint
|
||||
is used, all unique game IDs from the results must be fetched and
|
||||
the best match selected — not just the first result."""
|
||||
handler = IGDBHandler()
|
||||
|
||||
ecco_dolphin = _make_game(1799, "Ecco the Dolphin")
|
||||
ecco_tides = _make_game(5379, "Ecco: The Tides of Time")
|
||||
|
||||
# Primary search returns nothing useful
|
||||
async def mock_list_games(
|
||||
search_term=None, fields=None, where=None, limit=None
|
||||
):
|
||||
if where and "game_type" not in where and not where.startswith("("):
|
||||
# Primary search pass — return no results so we fall through to
|
||||
# the expanded search
|
||||
return []
|
||||
if where and where.startswith("("):
|
||||
# Expanded game details lookup — return both candidates
|
||||
return [ecco_dolphin, ecco_tides]
|
||||
return []
|
||||
|
||||
# Expanded search returns two results — wrong game FIRST, correct game second
|
||||
expanded_results = [
|
||||
{"game": {"id": 1799}, "name": "Ecco the Dolphin"},
|
||||
{"game": {"id": 5379}, "name": "Ecco: The Tides of Time"},
|
||||
]
|
||||
|
||||
with (
|
||||
patch(
|
||||
"handler.metadata.igdb_handler.IGDBHandler.is_enabled",
|
||||
return_value=True,
|
||||
),
|
||||
patch.object(
|
||||
handler.igdb_service,
|
||||
"list_games",
|
||||
side_effect=mock_list_games,
|
||||
),
|
||||
patch.object(
|
||||
handler.igdb_service,
|
||||
"search",
|
||||
new_callable=AsyncMock,
|
||||
return_value=expanded_results,
|
||||
),
|
||||
):
|
||||
result = await handler._search_rom(
|
||||
"ecco the tides of time", GENESIS_IGDB_ID, with_game_type=False
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result["id"] == 5379, (
|
||||
f"Expected Ecco: The Tides of Time (id=5379), got {result.get('name')} (id={result.get('id')}). "
|
||||
"The expanded search must consider ALL results, not just the first."
|
||||
)
|
||||
@@ -3,8 +3,13 @@ from unittest.mock import AsyncMock, patch
|
||||
import pytest
|
||||
|
||||
from handler.database import db_platform_handler, db_rom_handler
|
||||
from handler.metadata import meta_hasheous_handler, meta_playmatch_handler
|
||||
from handler.metadata import (
|
||||
meta_hasheous_handler,
|
||||
meta_playmatch_handler,
|
||||
meta_ra_handler,
|
||||
)
|
||||
from handler.metadata.hasheous_handler import HasheousRom
|
||||
from handler.metadata.ra_handler import RAGameRom
|
||||
from handler.scan_handler import MetadataSource, ScanType, scan_platform, scan_rom
|
||||
from models.platform import Platform
|
||||
from models.rom import Rom, RomFile
|
||||
@@ -174,3 +179,133 @@ async def test_scan_rom_complete_clears_unselected_metadata(
|
||||
assert result.ra_metadata == {}
|
||||
# Hasheous is still selected and should remain populated.
|
||||
assert result.hasheous_id == 999
|
||||
|
||||
|
||||
@patch.object(meta_playmatch_handler, "is_enabled", return_value=False)
|
||||
@patch.object(meta_ra_handler, "get_rom_by_id", new_callable=AsyncMock)
|
||||
@patch.object(meta_ra_handler, "get_rom", new_callable=AsyncMock)
|
||||
async def test_scan_rom_unmatched_fetches_ra_when_id_set_but_no_metadata(
|
||||
mock_get_rom, mock_get_rom_by_id, mock_playmatch_enabled
|
||||
):
|
||||
"""UNMATCHED scan must fetch RA metadata when ra_id is set manually but
|
||||
ra_metadata is empty (the user manually set the ID)."""
|
||||
ra_result = RAGameRom(
|
||||
ra_id=2774,
|
||||
name="Jak and Daxter: The Precursor's Legacy",
|
||||
url_cover="https://media.retroachievements.org/Images/jpg",
|
||||
)
|
||||
mock_get_rom_by_id.return_value = ra_result
|
||||
mock_get_rom.return_value = RAGameRom(ra_id=None)
|
||||
|
||||
platform = Platform(
|
||||
id=1,
|
||||
slug="ps2",
|
||||
fs_slug="ps2",
|
||||
name="PlayStation 2",
|
||||
igdb_id=8,
|
||||
ra_id=21,
|
||||
)
|
||||
platform = db_platform_handler.add_platform(platform)
|
||||
|
||||
# ROM has ra_id set manually but no ra_metadata (never fetched before)
|
||||
rom = Rom(
|
||||
platform_id=platform.id,
|
||||
fs_name="Jak and Daxter.chd",
|
||||
fs_name_no_tags="Jak and Daxter",
|
||||
fs_name_no_ext="Jak and Daxter",
|
||||
fs_extension="chd",
|
||||
fs_path="ps2",
|
||||
name="Jak and Daxter",
|
||||
ra_id=2774,
|
||||
ra_metadata={}, # empty - never fetched
|
||||
fs_size_bytes=1024,
|
||||
tags=[],
|
||||
)
|
||||
rom = db_rom_handler.add_rom(rom)
|
||||
|
||||
async with initialize_context():
|
||||
result = await scan_rom(
|
||||
platform=platform,
|
||||
scan_type=ScanType.UNMATCHED,
|
||||
rom=rom,
|
||||
fs_rom={
|
||||
"fs_name": "Jak and Daxter.chd",
|
||||
"flat": True,
|
||||
"nested": False,
|
||||
"files": [],
|
||||
"crc_hash": "",
|
||||
"md5_hash": "",
|
||||
"sha1_hash": "",
|
||||
"ra_hash": "",
|
||||
},
|
||||
metadata_sources=[MetadataSource.RA],
|
||||
newly_added=False,
|
||||
)
|
||||
|
||||
# ra_id was set manually - get_rom_by_id should be called, not get_rom
|
||||
mock_get_rom_by_id.assert_called_once()
|
||||
mock_get_rom.assert_not_called()
|
||||
assert result.ra_id == 2774
|
||||
|
||||
|
||||
@patch.object(meta_playmatch_handler, "is_enabled", return_value=False)
|
||||
@patch.object(meta_ra_handler, "get_rom_by_id", new_callable=AsyncMock)
|
||||
@patch.object(meta_ra_handler, "get_rom", new_callable=AsyncMock)
|
||||
async def test_scan_rom_unmatched_skips_ra_when_id_and_metadata_exist(
|
||||
mock_get_rom, mock_get_rom_by_id, mock_playmatch_enabled
|
||||
):
|
||||
"""UNMATCHED scan must NOT re-fetch RA metadata when both ra_id and
|
||||
ra_metadata are already populated."""
|
||||
mock_get_rom_by_id.return_value = RAGameRom(ra_id=None)
|
||||
mock_get_rom.return_value = RAGameRom(ra_id=None)
|
||||
|
||||
platform = Platform(
|
||||
id=1,
|
||||
slug="ps2",
|
||||
fs_slug="ps2",
|
||||
name="PlayStation 2",
|
||||
igdb_id=8,
|
||||
ra_id=21,
|
||||
)
|
||||
platform = db_platform_handler.add_platform(platform)
|
||||
|
||||
# ROM has both ra_id and ra_metadata populated
|
||||
rom = Rom(
|
||||
platform_id=platform.id,
|
||||
fs_name="Jak and Daxter.chd",
|
||||
fs_name_no_tags="Jak and Daxter",
|
||||
fs_name_no_ext="Jak and Daxter",
|
||||
fs_extension="chd",
|
||||
fs_path="ps2",
|
||||
name="Jak and Daxter",
|
||||
ra_id=2774,
|
||||
ra_metadata={"achievements_count": 60}, # already populated
|
||||
fs_size_bytes=1024,
|
||||
tags=[],
|
||||
)
|
||||
rom = db_rom_handler.add_rom(rom)
|
||||
|
||||
async with initialize_context():
|
||||
result = await scan_rom(
|
||||
platform=platform,
|
||||
scan_type=ScanType.UNMATCHED,
|
||||
rom=rom,
|
||||
fs_rom={
|
||||
"fs_name": "Jak and Daxter.chd",
|
||||
"flat": True,
|
||||
"nested": False,
|
||||
"files": [],
|
||||
"crc_hash": "",
|
||||
"md5_hash": "",
|
||||
"sha1_hash": "",
|
||||
"ra_hash": "",
|
||||
},
|
||||
metadata_sources=[MetadataSource.RA],
|
||||
newly_added=False,
|
||||
)
|
||||
|
||||
# Both ID and metadata exist - should not re-fetch
|
||||
mock_get_rom_by_id.assert_not_called()
|
||||
mock_get_rom.assert_not_called()
|
||||
# Existing ra_id should be preserved
|
||||
assert result.ra_id == 2774
|
||||
|
||||
10
frontend/package-lock.json
generated
10
frontend/package-lock.json
generated
@@ -15,7 +15,7 @@
|
||||
"bowser": "^2.14.1",
|
||||
"cronstrue": "^2.57.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"js-cookie": "^3.0.7",
|
||||
"lodash": "^4.18.1",
|
||||
"md-editor-v3": "^5.8.4",
|
||||
"mitt": "^3.0.1",
|
||||
@@ -7015,12 +7015,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/js-cookie": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.7.tgz",
|
||||
"integrity": "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"bowser": "^2.14.1",
|
||||
"cronstrue": "^2.57.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"js-cookie": "^3.0.7",
|
||||
"lodash": "^4.18.1",
|
||||
"md-editor-v3": "^5.8.4",
|
||||
"mitt": "^3.0.1",
|
||||
|
||||
Reference in New Issue
Block a user