@@ -717,3 +717,87 @@ func TestClient_StreamCustomHeader(t *testing.T) {
717717 }
718718
719719}
720+
721+ // TestClient_StreamHA_OneOriginDown tests that when in HA mode with multiple origins,
722+ // if one origin is down during initial connection, the stream should still be created
723+ func TestClient_StreamHA_OneOriginDown (t * testing.T ) {
724+ connectAttempts := & atomic.Uint64 {}
725+
726+ ms := newMockServer (func (w http.ResponseWriter , r * http.Request ) {
727+ if r .Method == http .MethodHead {
728+ w .Header ().Add (cllAvailOriginsHeader , "{001,002}" )
729+ w .WriteHeader (200 )
730+ return
731+ }
732+
733+ if r .URL .Path != apiV1WS {
734+ t .Errorf ("expected path %s, got %s" , apiV1WS , r .URL .Path )
735+ }
736+
737+ origin := r .Header .Get (cllOriginHeader )
738+ connectAttempts .Add (1 )
739+
740+ // Simulate origin 002 being down by timing out the connection
741+ if origin == "002" {
742+ w .WriteHeader (http .StatusGatewayTimeout )
743+ return
744+ }
745+
746+ // Origin 001 works fine
747+ conn , err := websocket .Accept (
748+ w , r , & websocket.AcceptOptions {CompressionMode : websocket .CompressionContextTakeover },
749+ )
750+
751+ if err != nil {
752+ t .Fatalf ("error accepting connection: %s" , err )
753+ }
754+ defer func () { _ = conn .CloseNow () }()
755+
756+ // Keep the connection alive for testing
757+ for conn .Ping (context .Background ()) == nil {
758+ time .Sleep (100 * time .Millisecond )
759+ }
760+ })
761+ defer ms .Close ()
762+
763+ streamsClient , err := ms .Client ()
764+ if err != nil {
765+ t .Fatalf ("error creating client %s" , err )
766+ }
767+
768+ cc := streamsClient .(* client )
769+ cc .config .Logger = LogPrintf
770+ cc .config .LogDebug = true
771+ cc .config .WsHA = true
772+
773+ // Attempt to create a stream - this should succeed with origin 001 even though 002 is down
774+ sub , err := streamsClient .Stream (context .Background (), []feed.ID {feed1 , feed2 })
775+
776+ // In HA mode, the stream should succeed even if one origin is down
777+ if err != nil {
778+ t .Errorf ("Stream creation failed: %v" , err )
779+ t .Errorf ("BUG DETECTED: In HA mode, stream should succeed with available origins" )
780+ t .Errorf ("Connect attempts made: %d" , connectAttempts .Load ())
781+ t .Errorf ("Expected: Stream should succeed with 1 active connection from origin 001" )
782+ t .Errorf ("Actual: Stream creation failed completely when origin 002 was unavailable" )
783+ t .Fatalf ("Test reveals bug: HA mode is not resilient to individual origin failures during initial connection" )
784+ }
785+ defer sub .Close ()
786+
787+ // Give connections time to establish
788+ time .Sleep (200 * time .Millisecond )
789+
790+ stats := sub .Stats ()
791+
792+ // In HA mode with 2 origins configured but 1 down, we should have:
793+ // - ConfiguredConnections: 2 (we tried to connect to both)
794+ // - ActiveConnections: 1 (only origin 001 is up)
795+ if stats .ConfiguredConnections != 2 {
796+ t .Errorf ("expected 2 configured connections, got %d" , stats .ConfiguredConnections )
797+ }
798+
799+ if stats .ActiveConnections != 1 {
800+ t .Errorf ("expected 1 active connection, got %d" , stats .ActiveConnections )
801+ }
802+
803+ }
0 commit comments