@@ -711,6 +711,103 @@ def _make_mock_file(self) -> MagicMock:
711711 f .file_name = "bios.bin"
712712 return f
713713
714+ def _make_unhashed_file (
715+ self , file_name : str = "Adventure Island II (USA).nes"
716+ ) -> MagicMock :
717+ """A top-level file with no hashes, as produced for NON_HASHABLE_PLATFORMS
718+ or when SKIP_HASH_CALCULATION is enabled."""
719+ f = MagicMock ()
720+ f .file_size_bytes = 131072
721+ f .is_top_level = True
722+ f .file_extension = "nes"
723+ f .md5_hash = ""
724+ f .sha1_hash = ""
725+ f .crc_hash = ""
726+ f .file_name = file_name
727+ f .archive_members = None
728+ return f
729+
730+ @pytest .mark .asyncio
731+ async def test_no_hash_still_attempts_jeuinfos_by_filename (self ):
732+ """A file with no hashes must still reach jeuInfos using the filename
733+ (romnom) + platform (systemeid), instead of bailing out and degrading to
734+ the weaker jeuRecherche name search."""
735+ handler = SSHandler ()
736+ mock_file = self ._make_unhashed_file ("Adventure Island II (USA).nes" )
737+ captured = {}
738+
739+ async def capture (** kwargs ):
740+ captured .update (kwargs )
741+ return None
742+
743+ with patch .object (handler .ss_service , "get_game_info" , side_effect = capture ):
744+ result , is_not_game = await handler .lookup_rom (
745+ MagicMock (platform_slug = "nes" ), 3 , [mock_file ]
746+ )
747+
748+ assert captured , "get_game_info should be called even without hashes"
749+ assert captured .get ("rom_name" ) == "Adventure Island II (USA).nes"
750+ assert captured .get ("system_id" ) == 3
751+ assert not captured .get ("md5" )
752+ assert not captured .get ("sha1" )
753+ assert not captured .get ("crc" )
754+ assert result ["ss_id" ] is None
755+ assert is_not_game is False
756+
757+ @pytest .mark .asyncio
758+ async def test_no_hash_match_builds_game (self ):
759+ """When jeuInfos matches an un-hashed file by filename, the game is built
760+ and returned (the romnom matcher bridges number-style differences such as
761+ 'Adventure Island II' -> 'Adventure Island 2')."""
762+ config = _make_config (region_priority = ["us" ])
763+ game = {
764+ "id" : "1234" ,
765+ "noms" : [{"region" : "us" , "text" : "Adventure Island 2" }],
766+ "medias" : [],
767+ "synopsis" : [],
768+ "dates" : [],
769+ "genres" : [],
770+ "familles" : [],
771+ "modes" : [],
772+ "joueurs" : {},
773+ "note" : {},
774+ }
775+ handler = SSHandler ()
776+ mock_file = self ._make_unhashed_file ("Adventure Island II (USA).nes" )
777+ rom = MagicMock (platform_slug = "nes" , platform_id = 1 , id = 100 , regions = ["USA" ])
778+
779+ with (
780+ patch ("handler.metadata.ss_handler.cm.get_config" , return_value = config ),
781+ patch .object (handler .ss_service , "get_game_info" , return_value = game ),
782+ ):
783+ result , is_not_game = await handler .lookup_rom (rom , 3 , [mock_file ])
784+
785+ assert result ["ss_id" ] == 1234
786+ assert result ["name" ] == "Adventure Island 2"
787+ assert is_not_game is False
788+
789+ @pytest .mark .asyncio
790+ async def test_no_hash_no_filename_skips_lookup (self ):
791+ """With neither a hash nor a filename there is nothing to match on, so the
792+ lookup is skipped without spending an API call."""
793+ handler = SSHandler ()
794+ mock_file = self ._make_unhashed_file (file_name = "" )
795+ called = False
796+
797+ async def capture (** kwargs ):
798+ nonlocal called
799+ called = True
800+ return None
801+
802+ with patch .object (handler .ss_service , "get_game_info" , side_effect = capture ):
803+ result , is_not_game = await handler .lookup_rom (
804+ MagicMock (platform_slug = "nes" ), 3 , [mock_file ]
805+ )
806+
807+ assert called is False
808+ assert result ["ss_id" ] is None
809+ assert is_not_game is False
810+
714811 @pytest .mark .asyncio
715812 async def test_returns_notgame_flag_on_notgame_field (self ):
716813 notgame_response = {
0 commit comments