@@ -587,3 +587,144 @@ func TestRun_EdgeAddrs_UnresolvableAddr(t *testing.T) {
587587 assert .Nil (t , report .SuggestedProtocol )
588588 assert .True (t , report .hasHardFail ())
589589}
590+
591+ // ---------------------------------------------------------------------------
592+ // Protocol override tests
593+ // ---------------------------------------------------------------------------
594+
595+ // TestRun_ProtocolOverride_HTTP2_BothPass verifies that when --protocol http2
596+ // is set and both transports are reachable, the summary reports HTTP/2 (not
597+ // QUIC, which would otherwise win the heuristic).
598+ func TestRun_ProtocolOverride_HTTP2_BothPass (t * testing.T ) {
599+ t .Parallel ()
600+ ctrl := gomock .NewController (t )
601+
602+ dns := mocks .NewMockDNSResolver (ctrl )
603+ tcp := mocks .NewMockTCPDialer (ctrl )
604+ quicD := mocks .NewMockQUICDialer (ctrl )
605+ mgmt := mocks .NewMockManagementDialer (ctrl )
606+ fakeQUICConn := newFakeQUICConn (ctrl )
607+
608+ dns .EXPECT ().Resolve (gomock .Any ()).Return (twoRegionAddrs (), nil )
609+ tcp .EXPECT ().DialEdge (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).
610+ Return (nopConn {}, nil ).AnyTimes ()
611+ quicD .EXPECT ().DialQuic (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).
612+ Return (fakeQUICConn , nil ).AnyTimes ()
613+ mgmt .EXPECT ().DialContext (gomock .Any (), gomock .Any (), gomock .Any ()).
614+ Return (nopConn {}, nil )
615+
616+ cfg := Config {
617+ Timeout : 2 * time .Second ,
618+ IPVersion : allregions .Auto ,
619+ ProtocolOverride : "http2" ,
620+ }
621+ report := Run (t .Context (), emptyCert , cfg , nopLogger (),
622+ RunDialers {DNSResolver : dns , TCPDialer : tcp , QUICDialer : quicD , ManagementDialer : mgmt })
623+
624+ // Both transports pass, but the override must win — HTTP/2 is reported.
625+ require .NotNil (t , report .SuggestedProtocol )
626+ assert .Equal (t , connection .HTTP2 , * report .SuggestedProtocol ,
627+ "override http2 should be reported even though QUIC probes also passed" )
628+ assert .False (t , report .hasHardFail ())
629+ }
630+
631+ // TestRun_ProtocolOverride_QUIC_BothPass verifies that when --protocol quic is
632+ // set and both transports are reachable, the summary reports QUIC (same as the
633+ // heuristic would choose, but driven by the override).
634+ func TestRun_ProtocolOverride_QUIC_BothPass (t * testing.T ) {
635+ t .Parallel ()
636+ ctrl := gomock .NewController (t )
637+
638+ dns := mocks .NewMockDNSResolver (ctrl )
639+ tcp := mocks .NewMockTCPDialer (ctrl )
640+ quicD := mocks .NewMockQUICDialer (ctrl )
641+ mgmt := mocks .NewMockManagementDialer (ctrl )
642+ fakeQUICConn := newFakeQUICConn (ctrl )
643+
644+ dns .EXPECT ().Resolve (gomock .Any ()).Return (twoRegionAddrs (), nil )
645+ tcp .EXPECT ().DialEdge (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).
646+ Return (nopConn {}, nil ).AnyTimes ()
647+ quicD .EXPECT ().DialQuic (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).
648+ Return (fakeQUICConn , nil ).AnyTimes ()
649+ mgmt .EXPECT ().DialContext (gomock .Any (), gomock .Any (), gomock .Any ()).
650+ Return (nopConn {}, nil )
651+
652+ cfg := Config {
653+ Timeout : 2 * time .Second ,
654+ IPVersion : allregions .Auto ,
655+ ProtocolOverride : "quic" ,
656+ }
657+ report := Run (t .Context (), emptyCert , cfg , nopLogger (),
658+ RunDialers {DNSResolver : dns , TCPDialer : tcp , QUICDialer : quicD , ManagementDialer : mgmt })
659+
660+ require .NotNil (t , report .SuggestedProtocol )
661+ assert .Equal (t , connection .QUIC , * report .SuggestedProtocol )
662+ assert .False (t , report .hasHardFail ())
663+ }
664+
665+ // TestRun_ProtocolOverride_HTTP2_QUICBlocked verifies that when --protocol http2
666+ // is set and QUIC is blocked, we still report HTTP/2 (not a fallback to the
667+ // heuristic, since the overridden transport is healthy).
668+ func TestRun_ProtocolOverride_HTTP2_QUICBlocked (t * testing.T ) {
669+ t .Parallel ()
670+ ctrl := gomock .NewController (t )
671+
672+ dns := mocks .NewMockDNSResolver (ctrl )
673+ tcp := mocks .NewMockTCPDialer (ctrl )
674+ quicD := mocks .NewMockQUICDialer (ctrl )
675+ mgmt := mocks .NewMockManagementDialer (ctrl )
676+
677+ dns .EXPECT ().Resolve (gomock .Any ()).Return (twoRegionAddrs (), nil )
678+ tcp .EXPECT ().DialEdge (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).
679+ Return (nopConn {}, nil ).AnyTimes ()
680+ quicD .EXPECT ().DialQuic (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).
681+ Return (nil , errors .New ("blocked" )).AnyTimes ()
682+ mgmt .EXPECT ().DialContext (gomock .Any (), gomock .Any (), gomock .Any ()).
683+ Return (nopConn {}, nil )
684+
685+ cfg := Config {
686+ Timeout : 2 * time .Second ,
687+ IPVersion : allregions .Auto ,
688+ ProtocolOverride : "http2" ,
689+ }
690+ report := Run (t .Context (), emptyCert , cfg , nopLogger (),
691+ RunDialers {DNSResolver : dns , TCPDialer : tcp , QUICDialer : quicD , ManagementDialer : mgmt })
692+
693+ require .NotNil (t , report .SuggestedProtocol )
694+ assert .Equal (t , connection .HTTP2 , * report .SuggestedProtocol )
695+ assert .False (t , report .hasHardFail ())
696+ }
697+
698+ // TestRun_ProtocolOverride_HTTP2_BothBlocked verifies that when --protocol http2
699+ // is set but the HTTP/2 transport itself also fails (hard fail), the override
700+ // falls through to the heuristic which returns nil — there is no usable protocol.
701+ func TestRun_ProtocolOverride_HTTP2_BothBlocked (t * testing.T ) {
702+ t .Parallel ()
703+ ctrl := gomock .NewController (t )
704+
705+ dns := mocks .NewMockDNSResolver (ctrl )
706+ tcp := mocks .NewMockTCPDialer (ctrl )
707+ quicD := mocks .NewMockQUICDialer (ctrl )
708+ mgmt := mocks .NewMockManagementDialer (ctrl )
709+
710+ dns .EXPECT ().Resolve (gomock .Any ()).Return (twoRegionAddrs (), nil )
711+ tcp .EXPECT ().DialEdge (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).
712+ Return (nil , errors .New ("blocked" )).AnyTimes ()
713+ quicD .EXPECT ().DialQuic (gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any (), gomock .Any ()).
714+ Return (nil , errors .New ("blocked" )).AnyTimes ()
715+ mgmt .EXPECT ().DialContext (gomock .Any (), gomock .Any (), gomock .Any ()).
716+ Return (nopConn {}, nil )
717+
718+ cfg := Config {
719+ Timeout : 2 * time .Second ,
720+ IPVersion : allregions .Auto ,
721+ ProtocolOverride : "http2" ,
722+ }
723+ report := Run (t .Context (), emptyCert , cfg , nopLogger (),
724+ RunDialers {DNSResolver : dns , TCPDialer : tcp , QUICDialer : quicD , ManagementDialer : mgmt })
725+
726+ // The overridden transport (HTTP/2) is blocked, so the override cannot be
727+ // honoured and the hard-fail path reports no suggested protocol.
728+ assert .Nil (t , report .SuggestedProtocol )
729+ assert .True (t , report .hasHardFail ())
730+ }
0 commit comments