@@ -164,6 +164,12 @@ public function testSellerStatusOnCacheMissFetchesAndStoresResult(): void
164164 PartnersEndpoint::SELLER_STATUS_CACHE_TTL
165165 );
166166
167+ $ this ->failure_registry
168+ ->shouldReceive ('has_failure_in_timeframe ' )
169+ ->once ()
170+ ->with (FailureRegistry::SELLER_STATUS_KEY , PartnersEndpoint::SELLER_STATUS_CACHE_TTL )
171+ ->andReturn (false );
172+
167173 $ this ->seller_status_factory
168174 ->shouldReceive ('from_paypal_response ' )
169175 ->once ()
@@ -203,6 +209,12 @@ public function testSellerStatusOnApiErrorRegistersFailureAndThrows(): void
203209 ->shouldReceive ('set ' )
204210 ->never ();
205211
212+ $ this ->failure_registry
213+ ->shouldReceive ('has_failure_in_timeframe ' )
214+ ->once ()
215+ ->with (FailureRegistry::SELLER_STATUS_KEY , PartnersEndpoint::SELLER_STATUS_CACHE_TTL )
216+ ->andReturn (false );
217+
206218 $ this ->failure_registry
207219 ->shouldReceive ('add_failure ' )
208220 ->once ()
@@ -247,6 +259,12 @@ public function testSellerStatusOnApiErrorUsesFallbackFilter(): void
247259 PartnersEndpoint::SELLER_STATUS_CACHE_TTL
248260 );
249261
262+ $ this ->failure_registry
263+ ->shouldReceive ('has_failure_in_timeframe ' )
264+ ->once ()
265+ ->with (FailureRegistry::SELLER_STATUS_KEY , PartnersEndpoint::SELLER_STATUS_CACHE_TTL )
266+ ->andReturn (false );
267+
250268 $ this ->failure_registry
251269 ->shouldReceive ('add_failure ' )
252270 ->once ()
@@ -267,6 +285,102 @@ public function testSellerStatusOnApiErrorUsesFallbackFilter(): void
267285 $ this ->assertSame ($ fallback , $ result );
268286 }
269287
288+ // -----------------------------------------------------------------------
289+ // Tests: seller_status() failure backoff
290+ // -----------------------------------------------------------------------
291+
292+ /**
293+ * GIVEN the transient cache is empty
294+ * AND a recent failure is registered within the backoff window
295+ * WHEN seller_status() is called
296+ * THEN no HTTP request is made to the PayPal API
297+ * AND no additional failure is registered
298+ * AND a RuntimeException is thrown (no fallback configured)
299+ */
300+ public function testSellerStatusBacksOffWhenRecentFailureRegistered (): void
301+ {
302+ $ this ->cache
303+ ->shouldReceive ('get ' )
304+ ->once ()
305+ ->with (PartnersEndpoint::SELLER_STATUS_CACHE_KEY )
306+ ->andReturn (false );
307+
308+ $ this ->cache
309+ ->shouldReceive ('set ' )
310+ ->never ();
311+
312+ $ this ->failure_registry
313+ ->shouldReceive ('has_failure_in_timeframe ' )
314+ ->once ()
315+ ->with (FailureRegistry::SELLER_STATUS_KEY , PartnersEndpoint::SELLER_STATUS_CACHE_TTL )
316+ ->andReturn (true );
317+
318+ $ this ->failure_registry
319+ ->shouldReceive ('add_failure ' )
320+ ->never ();
321+
322+ when ('apply_filters ' )->alias (function (string $ hook , ...$ args ) {
323+ return $ args [0 ] ?? null ;
324+ });
325+
326+ expect ('wp_remote_get ' )->never ();
327+
328+ $ this ->expectException (\RuntimeException::class);
329+
330+ $ this ->make_endpoint ()->seller_status ();
331+ }
332+
333+ /**
334+ * GIVEN the transient cache is empty
335+ * AND a recent failure is registered within the backoff window
336+ * AND the fallback filter returns a SellerStatus
337+ * WHEN seller_status() is called
338+ * THEN no HTTP request is made to the PayPal API
339+ * AND the fallback is returned and stored in the cache
340+ */
341+ public function testSellerStatusBackoffUsesFallbackWhenConfigured (): void
342+ {
343+ $ fallback = Mockery::mock (SellerStatus::class);
344+
345+ $ this ->cache
346+ ->shouldReceive ('get ' )
347+ ->once ()
348+ ->with (PartnersEndpoint::SELLER_STATUS_CACHE_KEY )
349+ ->andReturn (false );
350+
351+ $ this ->cache
352+ ->shouldReceive ('set ' )
353+ ->once ()
354+ ->with (
355+ PartnersEndpoint::SELLER_STATUS_CACHE_KEY ,
356+ $ fallback ,
357+ PartnersEndpoint::SELLER_STATUS_CACHE_TTL
358+ );
359+
360+ $ this ->failure_registry
361+ ->shouldReceive ('has_failure_in_timeframe ' )
362+ ->once ()
363+ ->with (FailureRegistry::SELLER_STATUS_KEY , PartnersEndpoint::SELLER_STATUS_CACHE_TTL )
364+ ->andReturn (true );
365+
366+ $ this ->failure_registry
367+ ->shouldReceive ('add_failure ' )
368+ ->never ();
369+
370+ when ('apply_filters ' )->alias (function (string $ hook , ...$ args ) use ($ fallback ) {
371+ if ($ hook === 'woocommerce_paypal_payments_seller_status_fallback ' ) {
372+ return $ fallback ;
373+ }
374+ return $ args [0 ] ?? null ;
375+ });
376+
377+ expect ('wp_remote_get ' )->never ();
378+
379+ $ result = $ this ->make_endpoint ()->seller_status ();
380+
381+ $ this ->assertSame ($ fallback , $ result );
382+ }
383+
270384 // -----------------------------------------------------------------------
271385 // Tests: clear_seller_status_cache()
272386 // -----------------------------------------------------------------------
0 commit comments