@@ -305,6 +305,130 @@ func TestPool_PerEntrySettings(t *testing.T) {
305305 require .Len (t , calls , 2 )
306306}
307307
308+ func TestPool_Execute_AllOpenFallbackRecovers (t * testing.T ) {
309+ entries := []Entry [testState ]{
310+ {State : testState {url : "https://ca1.example.com" }, Priority : 100 },
311+ {State : testState {url : "https://ca2.example.com" }, Priority : 50 },
312+ }
313+ pool := New [string ](entries , testDefaults (time .Minute ))
314+
315+ // Trip both breakers.
316+ _ , _ = pool .Execute (func (s testState ) (string , error ) {
317+ return "" , & infraError {msg : "down" }
318+ })
319+ require .True (t , pool .AllUnavailable ())
320+
321+ // Network recovers — fallback should try directly and succeed.
322+ result , err := pool .Execute (func (s testState ) (string , error ) {
323+ return "recovered" , nil
324+ })
325+ require .NoError (t , err )
326+ require .Equal (t , "recovered" , result )
327+ }
328+
329+ func TestPool_Execute_AllOpenFallbackStillFails (t * testing.T ) {
330+ entries := []Entry [testState ]{
331+ {State : testState {url : "https://ca1.example.com" }, Priority : 100 },
332+ {State : testState {url : "https://ca2.example.com" }, Priority : 50 },
333+ }
334+ pool := New [string ](entries , testDefaults (time .Minute ))
335+
336+ // Trip both breakers.
337+ _ , _ = pool .Execute (func (s testState ) (string , error ) {
338+ return "" , & infraError {msg : "down" }
339+ })
340+
341+ // Still failing — should return AllUnavailableError.
342+ _ , err := pool .Execute (func (s testState ) (string , error ) {
343+ return "" , & infraError {msg : "still down" }
344+ })
345+ require .Error (t , err )
346+ var allUnavail * AllUnavailableError
347+ require .ErrorAs (t , err , & allUnavail )
348+ require .Contains (t , allUnavail .Error (), "still down" )
349+ }
350+
351+ func TestPool_Execute_AllOpenFallbackPriorityOrder (t * testing.T ) {
352+ entries := []Entry [testState ]{
353+ {State : testState {url : "https://ca-low.example.com" }, Priority : 50 },
354+ {State : testState {url : "https://ca-high.example.com" }, Priority : 100 },
355+ }
356+ pool := New [string ](entries , testDefaults (time .Minute ))
357+
358+ // Trip both breakers.
359+ _ , _ = pool .Execute (func (s testState ) (string , error ) {
360+ return "" , & infraError {msg : "down" }
361+ })
362+
363+ // Track call order in fallback.
364+ var calls []string
365+ _ , _ = pool .Execute (func (s testState ) (string , error ) {
366+ calls = append (calls , s .url )
367+ return "" , & infraError {msg : "down" }
368+ })
369+
370+ // Should try high priority first, then low.
371+ require .Equal (t , []string {"https://ca-high.example.com" , "https://ca-low.example.com" }, calls )
372+ }
373+
374+ func TestPool_Execute_AllOpenFallbackFirstSuccessWins (t * testing.T ) {
375+ entries := []Entry [testState ]{
376+ {State : testState {url : "https://ca1.example.com" }, Priority : 100 },
377+ {State : testState {url : "https://ca2.example.com" }, Priority : 100 },
378+ {State : testState {url : "https://ca3.example.com" }, Priority : 50 },
379+ }
380+ pool := New [string ](entries , testDefaults (time .Minute ))
381+
382+ // Trip all breakers.
383+ for i := 0 ; i < 3 ; i ++ {
384+ _ , _ = pool .Execute (func (s testState ) (string , error ) {
385+ return "" , & infraError {msg : "down" }
386+ })
387+ }
388+ require .True (t , pool .AllUnavailable ())
389+
390+ // Second entry succeeds — third should not be tried.
391+ var calls []string
392+ result , err := pool .Execute (func (s testState ) (string , error ) {
393+ calls = append (calls , s .url )
394+ if s .url == "https://ca2.example.com" {
395+ return "from-ca2" , nil
396+ }
397+ return "" , & infraError {msg : "down" }
398+ })
399+ require .NoError (t , err )
400+ require .Equal (t , "from-ca2" , result )
401+ require .Equal (t , []string {"https://ca1.example.com" , "https://ca2.example.com" }, calls )
402+ }
403+
404+ func TestPool_Status (t * testing.T ) {
405+ entries := []Entry [testState ]{
406+ {State : testState {url : "https://ca1.example.com" }, Priority : 100 },
407+ {State : testState {url : "https://ca2.example.com" }, Priority : 50 },
408+ }
409+ pool := New [string ](entries , testDefaults (time .Minute ))
410+
411+ // Both should start closed.
412+ statuses := pool .Status ()
413+ require .Len (t , statuses , 2 )
414+ require .Equal (t , "closed" , statuses [0 ].BreakerState )
415+ require .Equal (t , "closed" , statuses [1 ].BreakerState )
416+ require .Equal (t , 100 , statuses [0 ].Priority )
417+ require .Equal (t , 50 , statuses [1 ].Priority )
418+
419+ // Trip the high-priority breaker.
420+ _ , _ = pool .Execute (func (s testState ) (string , error ) {
421+ if s .url == "https://ca1.example.com" {
422+ return "" , & infraError {msg : "down" }
423+ }
424+ return "ok" , nil
425+ })
426+
427+ statuses = pool .Status ()
428+ require .Equal (t , "open" , statuses [0 ].BreakerState )
429+ require .Equal (t , "closed" , statuses [1 ].BreakerState )
430+ }
431+
308432func TestPool_Execute_StringState (t * testing.T ) {
309433 // Test with simple string state (common case)
310434 entries := []Entry [string ]{
0 commit comments