def test_skip_cache_read(skip, requests_mock): path = 'test_skip_cache_read' url = MOCK_BASE + path requests_mock.get(url, [{'text': 'first'}, {'text': 'second'}]) source = _get_source(None) session = cast(CacheMixin, source._session) reqdata = ReqData(path=path) res = source.get(reqdata, skip_cache_read=skip) # response should have been stored in cache either way assert url in session.cache.urls # first response is never from cache assert res.from_cache is False # type: ignore assert res.text == 'first' res = source.get(reqdata, skip_cache_read=skip) # second response should only be from cache if skip_cache_read=False assert res.from_cache is (not skip) # type: ignore # if skip_cache_read=True, new request should have been sent (and stored) expected_text = 'second' if skip else 'first' assert res.text == expected_text # new request with skip_cache_read=False should return same data as previous one res = source.get(reqdata, skip_cache_read=False) assert res.from_cache is True # type: ignore assert res.text == expected_text
def test_skip_cache_write(skip, requests_mock): path = 'test_skip_cache_write' url = MOCK_BASE + path requests_mock.get(url, [{ 'text': 'first' }, { 'text': 'second' }, { 'text': 'third' }]) source = _get_source(None) session = cast(CacheMixin, source._session) reqdata = ReqData(path=path) res = source.get(reqdata, skip_cache_write=True) # response should not be in cache assert url not in session.cache.urls assert res.text == 'first' res = source.get(reqdata, skip_cache_write=skip) # second response should be in cache only if skip_cache_write=False assert (url in session.cache.urls) is not skip assert res.from_cache is False # type: ignore assert res.text == 'second' res = source.get(reqdata, skip_cache_write=False) # response should be in cache assert url in session.cache.urls assert res.from_cache is not skip # type: ignore # if second response was not written to cache, we should've gotten the third one now expected_text = 'third' if skip else 'second' assert res.text == expected_text
def test_config__timeout(): source = _get_source( SourceConfig(timeout=42, response_status_checking=StatusCheckMode.NONE)) with patch.object(source._session, 'get') as mock_get: source.get(ReqData(path=MOCK_PATH)) assert mock_get.call_args[1]['timeout'] == 42
def get_dlcs_for_title( self, title: Union[ids.TContentIDInput, SamuraiListTitle, SamuraiTitleElement], **kwargs: Any) -> Union[SamuraiTitleDlcsWiiU, SamuraiTitleDlcs3DS]: # note: this endpoint doesn't seem to be reliable for all titles, # some titles have aoc/iap but this response is empty if isinstance(title, (SamuraiListTitle, SamuraiTitleElement)): content_id = title.content_id else: content_id = ids.ContentID.get_inst(title) dlcs_type: Union[SamuraiTitleDlcs3DS, SamuraiTitleDlcsWiiU] if content_id.type.platform == ids.ContentPlatform._3DS: dlcs_type = SamuraiTitleDlcs3DS() params = {} # 3DS DLC results aren't paginated elif content_id.type.platform == ids.ContentPlatform.WIIU: dlcs_type = SamuraiTitleDlcsWiiU() params = { 'limit': 200 } # assuming a maximum of 200 DLCs per title, seems reasonable else: assert False # unhandled, should never happen return self._create_type( ReqData(path=f'title/{ids.ContentID.get_str(content_id)}/aocs', params=params), dlcs_type, **kwargs)
def __init__(self, region: Union[str, Region], shop_id: int, *, lang: Optional[str] = None, cdn: Optional[bool] = False, config: Optional[SourceConfig] = None): params: RequestDict = {'shop_id': shop_id} if lang: params['lang'] = lang region = region.country_code if isinstance(region, Region) else region if cdn: host = 'samurai-wup.cdn.nintendo.net' fingerprint = '43:8D:A9:4A:60:CB:00:DF:F2:B3:EB:17:A7:A2:1C:98:BD:11:FC:4A:A6:49:62:C1:2C:EF:41:BB:1F:28:88:95' else: host = 'samurai.wup.shop.nintendo.net' fingerprint = 'C6:6E:7D:66:D0:73:62:2F:A3:28:7F:A6:2F:F5:73:5C:71:EE:EB:3D:93:AC:B3:14:7A:8F:85:B4:07:D4:CE:ED' super().__init__(ReqData(path=f'https://{host}/samurai/ws/{region}/', params=params), config, verify_tls=False, require_fingerprint=fingerprint) self.region = region self.shop_id = shop_id self.lang = lang
def get_ec_info(self, content_id: ids.TContentIDInput, **kwargs: Any) -> NinjaEcInfo: return self._create_type( ReqData( path= f'{self.region}/title/{ids.ContentID.get_str(content_id)}/ec_info' ), NinjaEcInfo(), **kwargs)
def get_tmd(self, title_id: ids.TTitleIDInput, version: Optional[int] = None, **kwargs: Any) -> TMD: return self._create_type( ReqData(path=f'{ids.TitleID.get_str(title_id)}/tmd' + (f'.{version}' if version is not None else '')), TMD(title_id), **kwargs)
def get_latest_updatelist_version(self, *, skip_cache_read: bool = True, **kwargs: Any) -> UpdateListVersion: return self._create_type(ReqData(path='latest_version'), UpdateListVersion(), skip_cache_read=skip_cache_read, **kwargs)
def __init__(self, cert: CertType, config: Optional[SourceConfig] = None): super().__init__( ReqData(path='https://ccs.c.shop.nintendowifi.net/ccs/download/', cert=cert), config, verify_tls=False, require_fingerprint= 'E9:74:4D:71:E3:06:6A:84:80:1D:0B:52:5E:26:8E:80:70:41:F4:20')
def test_cached_nolimit(mock_sleep): source = new_source(cache=True) reqdata = ReqData(path=MOCK_URL) assert not source.get(reqdata).from_cache assert mock_sleep.call_count == 0 assert source.get(reqdata).from_cache assert mock_sleep.call_count == 0
def _get_list(self, list_type: Type[_TList], path: str, offset: int, limit: int, other_params: RequestDict, **kwargs: Any) -> _TList: return self._create_type( ReqData(path=path, params={ 'offset': offset, 'limit': limit, **other_params }), list_type(), **kwargs)
def get_dlc_prices(self, *dlcs: Union[ids.TContentIDInput, SamuraiDlcWiiU], **kwargs: Any) -> SamuraiDlcPrices: return self._create_type( ReqData(path='aocs/prices', params={ 'aoc[]': ','.join( ids.ContentID.get_str(i) for i in self.__get_dlc_ids(dlcs)) }), SamuraiDlcPrices(), **kwargs)
def get_app(self, title_id: ids.TTitleIDInput, content_id: int, *, skip_cache: bool = True, **kwargs: Any) -> UnloadableType: return self._create_type( ReqData(path=f'{ids.TitleID.get_str(title_id)}/{content_id:08X}'), skip_cache=skip_cache, **kwargs)
def test_unloadable(skip_cache): source = _get_source(None) reqdata = ReqData(path=MOCK_PATH) inst = source._create_type(reqdata, skip_cache=skip_cache) assert isinstance(inst, UnloadableType) assert inst.reqdata is reqdata assert inst.kwargs['skip_cache'] is skip_cache with inst.get_reader() as reader: assert reader.read() == b'response'
def __init__(self, config: Optional[SourceConfig] = None): super().__init__( ReqData( path= 'https://tagaya-wup.cdn.nintendo.net/tagaya/versionlist/EUR/GB/' ), config, verify_tls=False, require_fingerprint= '43:8D:A9:4A:60:CB:00:DF:F2:B3:EB:17:A7:A2:1C:98:BD:11:FC:4A:A6:49:62:C1:2C:EF:41:BB:1F:28:88:95' )
def __init__(self, config: Optional[SourceConfig] = None): super().__init__( ReqData( path= 'https://tagaya.wup.shop.nintendo.net/tagaya/versionlist/EUR/GB/' ), config, verify_tls=False, require_fingerprint= 'C6:6E:7D:66:D0:73:62:2F:A3:28:7F:A6:2F:F5:73:5C:71:EE:EB:3D:93:AC:B3:14:7A:8F:85:B4:07:D4:CE:ED' )
def get_idbe(self, title_id: ids.TTitleIDInput, version: Optional[int] = None, **kwargs: Any) -> IDBE: tid_str = ids.TitleID.get_str(title_id) return self._create_type( # server seems to ignore first value, it probably doesn't matter what is supplied here # based on nn_idbe.rpl .text+0x1d0 ReqData(path=f'{tid_str[12:14]}/{tid_str}' + (f'-{version}' if version is not None else '') + '.idbe'), IDBE(title_id), **kwargs)
def get_dlcs_wiiu(self, *dlc_ids: ids.TContentIDInput, **kwargs: Any) -> SamuraiDlcsWiiU: for dlc_id in dlc_ids: dlc_id = ids.ContentID.get_inst(dlc_id) if dlc_id.type.platform != ids.ContentPlatform.WIIU: raise ValueError(f'content ID {dlc_id} is not a WiiU title') return self._create_type( ReqData(path='aocs', params={ 'aoc[]': ','.join(ids.ContentID.get_str(i) for i in dlc_ids) }), SamuraiDlcsWiiU(), **kwargs)
def __init__(self, platform: str, config: Optional[SourceConfig] = None): # platform does not matter, both servers seem to contain the same data if platform not in ('wup', 'ctr'): raise ValueError('`platform` must be either \'wup\' or \'ctr\'') super().__init__( ReqData( path=f'https://idbe-{platform}.cdn.nintendo.net/icondata/'), config, verify_tls=False, # fingerprints for ctr/wup certs are the same require_fingerprint= '43:8D:A9:4A:60:CB:00:DF:F2:B3:EB:17:A7:A2:1C:98:BD:11:FC:4A:A6:49:62:C1:2C:EF:41:BB:1F:28:88:95' ) self.platform = platform
def test_skip_cache(skip, expected_cache_status, callable): source = _get_source(None) reqdata = ReqData(path=MOCK_PATH) if callable: skip_val = skip def skip(r): # noqa assert isinstance(r, requests.PreparedRequest) return skip_val for expected in expected_cache_status: result = source.get(reqdata, skip_cache=skip) assert result.from_cache is expected # type: ignore assert result.content == b'response'
def __init__(self, region: Union[str, Region], cert: CertType, config: Optional[SourceConfig] = None): super().__init__( ReqData(path='https://ninja.wup.shop.nintendo.net/ninja/ws/', cert=cert), config, verify_tls=False, require_fingerprint= 'C6:6E:7D:66:D0:73:62:2F:A3:28:7F:A6:2F:F5:73:5C:71:EE:EB:3D:93:AC:B3:14:7A:8F:85:B4:07:D4:CE:ED' ) self.region = region.country_code if isinstance(region, Region) else region
def get_id_pair(self, *, content_id: Optional[ids.TContentIDInput] = None, title_id: Optional[ids.TTitleIDInput] = None, **kwargs: Any) -> NinjaIDPair: if (content_id is None) == (title_id is None): raise ValueError( 'Exactly one of `content_id`/`title_id` must be set') return self._create_type( ReqData(path='titles/id_pair', params={'title_id[]': ids.TitleID.get_str(title_id)} if title_id else { 'ns_uid[]': ids.ContentID.get_str( cast(ids.TContentIDInput, content_id)) }), NinjaIDPair(), **kwargs)
def __init__(self, config: Optional[SourceConfig] = None): super().__init__( ReqData( path='http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/'), config)
def check(code): result = func(ReqData(path=f'code/{code}')) if hasattr(result, '__enter__'): with result: pass
def get_title(self, content_id: ids.TContentIDInput, **kwargs: Any) -> SamuraiTitle: return self._create_type( ReqData(path=f'title/{ids.ContentID.get_str(content_id)}'), SamuraiTitle(), **kwargs)
def get_telops(self, **kwargs: Any) -> SamuraiTelops: return self._create_type(ReqData(path='telops'), SamuraiTelops(), **kwargs)
def get_news(self, **kwargs: Any) -> SamuraiNews: return self._create_type(ReqData(path='news'), SamuraiNews(), **kwargs)
def get_demo(self, content_id: ids.TContentIDInput, **kwargs: Any) -> SamuraiDemo: return self._create_type( ReqData(path=f'demo/{ids.ContentID.get_str(content_id)}'), SamuraiDemo(), **kwargs)
def get_h3(self, title_id: ids.TTitleIDInput, content_id: int, **kwargs: Any) -> UnloadableType: return self._create_type( ReqData( path=f'{ids.TitleID.get_str(title_id)}/{content_id:08X}.h3'), **kwargs)
def get_cetk(self, title_id: ids.TTitleIDInput, **kwargs: Any) -> Ticket: return self._create_type( ReqData(path=f'{ids.TitleID.get_str(title_id)}/cetk'), Ticket(title_id), **kwargs)