@@ -253,6 +253,62 @@ async def test_pull_hub_fallback(self, config: MusherConfig):
253253 assert bundle .version == "1.0.0"
254254 assert len (bundle .files ()) == 1
255255
256+ @respx .mock
257+ async def test_pull_hub_fallback_401 (self , config : MusherConfig ):
258+ """When namespaced :pull returns 401, falls back to hub :pull."""
259+ respx .get (f"{ _BASE } /v1/namespaces/myorg/bundles/my-bundle:resolve" ).mock (
260+ return_value = httpx .Response (200 , json = _RESOLVE_RESPONSE )
261+ )
262+ # Namespaced :pull returns 401 (no API key)
263+ respx .get (f"{ _BASE } /v1/namespaces/myorg/bundles/my-bundle/versions/1.0.0:pull" ).mock (
264+ return_value = httpx .Response (401 , json = {"detail" : "Invalid or missing API token" })
265+ )
266+ # Hub :pull succeeds
267+ respx .get (f"{ _BASE } /v1/hub/bundles/myorg/my-bundle/versions/1.0.0:pull" ).mock (
268+ return_value = httpx .Response (200 , json = _PULL_RESPONSE )
269+ )
270+ async with AsyncClient (config = config ) as client :
271+ bundle = await client .pull ("myorg/my-bundle:1.0.0" )
272+ assert isinstance (bundle , Bundle )
273+ assert bundle .version == "1.0.0"
274+ assert len (bundle .files ()) == 1
275+
276+ @respx .mock
277+ async def test_resolve_hub_fallback_401 (self , config : MusherConfig ):
278+ """When namespaced :resolve returns 401, falls back to hub :resolve."""
279+ respx .get (f"{ _BASE } /v1/namespaces/myorg/bundles/my-bundle:resolve" ).mock (
280+ return_value = httpx .Response (401 , json = {"detail" : "Invalid or missing API token" })
281+ )
282+ respx .get (f"{ _BASE } /v1/hub/bundles/myorg/my-bundle:resolve" ).mock (
283+ return_value = httpx .Response (200 , json = _RESOLVE_RESPONSE )
284+ )
285+ async with AsyncClient (config = config ) as client :
286+ result = await client .resolve ("myorg/my-bundle:1.0.0" )
287+ assert isinstance (result , ResolveResult )
288+ assert result .version == "1.0.0"
289+
290+ @respx .mock
291+ async def test_resolve_hub_fallback_403 (self , config : MusherConfig ):
292+ """When namespaced :resolve returns 403, falls back to hub :resolve."""
293+ respx .get (f"{ _BASE } /v1/namespaces/myorg/bundles/my-bundle:resolve" ).mock (
294+ return_value = httpx .Response (
295+ 403 ,
296+ json = {
297+ "type" : "https://api.platform.musher.dev/errors/forbidden" ,
298+ "title" : "Forbidden" ,
299+ "status" : 403 ,
300+ "detail" : "Not authorized" ,
301+ },
302+ )
303+ )
304+ respx .get (f"{ _BASE } /v1/hub/bundles/myorg/my-bundle:resolve" ).mock (
305+ return_value = httpx .Response (200 , json = _RESOLVE_RESPONSE )
306+ )
307+ async with AsyncClient (config = config ) as client :
308+ result = await client .resolve ("myorg/my-bundle:1.0.0" )
309+ assert isinstance (result , ResolveResult )
310+ assert result .version == "1.0.0"
311+
256312
257313class TestClient :
258314 def test_instantiation (self , config : MusherConfig ):
0 commit comments