github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/core/connection/manager_test.go (about)

     1  /*
     2   * Copyright (C) 2017 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package connection
    19  
    20  import (
    21  	"context"
    22  	"errors"
    23  	"fmt"
    24  	"math/big"
    25  	"net"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/ethereum/go-ethereum/common"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/suite"
    33  	"google.golang.org/protobuf/proto"
    34  
    35  	"github.com/mysteriumnetwork/node/communication/nats"
    36  	"github.com/mysteriumnetwork/node/core/connection/connectionstate"
    37  	"github.com/mysteriumnetwork/node/core/discovery/proposal"
    38  	"github.com/mysteriumnetwork/node/core/ip"
    39  	"github.com/mysteriumnetwork/node/core/location"
    40  	"github.com/mysteriumnetwork/node/core/location/locationstate"
    41  	"github.com/mysteriumnetwork/node/identity"
    42  	"github.com/mysteriumnetwork/node/market"
    43  	"github.com/mysteriumnetwork/node/mocks"
    44  	"github.com/mysteriumnetwork/node/p2p"
    45  	"github.com/mysteriumnetwork/node/pb"
    46  	"github.com/mysteriumnetwork/node/session"
    47  	"github.com/mysteriumnetwork/node/session/connectivity"
    48  	"github.com/mysteriumnetwork/node/trace"
    49  )
    50  
    51  type testContext struct {
    52  	suite.Suite
    53  	fakeConnectionFactory *connectionFactoryFake
    54  	connManager           *connectionManager
    55  	MockPaymentIssuer     *MockPaymentIssuer
    56  	stubPublisher         *mocks.EventBus
    57  	mockStatistics        connectionstate.Statistics
    58  	fakeIPResolver        ip.Resolver
    59  	fakeLocationResolver  location.OriginResolver
    60  	config                Config
    61  	statsReportInterval   time.Duration
    62  	mockP2P               *mockP2PDialer
    63  	mockTime              time.Time
    64  	sync.RWMutex
    65  }
    66  
    67  var (
    68  	consumerID            = identity.FromAddress("identity-1")
    69  	consumerLocation      = locationstate.Location{Country: "CH"}
    70  	activeProviderID      = identity.FromAddress("fake-node-1")
    71  	hermesID              = common.HexToAddress("hermes")
    72  	activeProviderContact = market.Contact{
    73  		Type:       p2p.ContactTypeV1,
    74  		Definition: p2p.ContactDefinition{},
    75  	}
    76  	activeServiceType = "fake-service"
    77  	activeProposal    = proposal.PricedServiceProposal{
    78  		ServiceProposal: market.ServiceProposal{
    79  			ProviderID:  activeProviderID.Address,
    80  			Contacts:    []market.Contact{activeProviderContact},
    81  			ServiceType: activeServiceType,
    82  			Location:    market.Location{},
    83  		},
    84  		Price: market.Price{
    85  			PricePerHour: big.NewInt(1),
    86  			PricePerGiB:  big.NewInt(2),
    87  		},
    88  	}
    89  	activeProposalLookup = func() (proposal *proposal.PricedServiceProposal, err error) {
    90  		return &activeProposal, nil
    91  	}
    92  	establishedSessionID = session.ID("session-100")
    93  )
    94  
    95  func (tc *testContext) SetupTest() {
    96  	tc.Lock()
    97  	defer tc.Unlock()
    98  
    99  	tc.stubPublisher = mocks.NewEventBus()
   100  	tc.mockStatistics = connectionstate.Statistics{
   101  		BytesReceived: 10,
   102  		BytesSent:     20,
   103  	}
   104  	tc.fakeConnectionFactory = &connectionFactoryFake{
   105  		mockError: nil,
   106  		mockConnection: &connectionMock{
   107  			onStartReportStates: []fakeState{
   108  				processStarted,
   109  				connectingState,
   110  				waitState,
   111  				authenticatingState,
   112  				getConfigState,
   113  				assignIPState,
   114  				connectedState,
   115  			},
   116  			onStopReportStates: []fakeState{
   117  				exitingState,
   118  				processExited,
   119  			},
   120  			onStartReportStats: tc.mockStatistics,
   121  		},
   122  	}
   123  
   124  	tc.config = Config{
   125  		IPCheck: IPCheckConfig{
   126  			MaxAttempts:             3,
   127  			SleepDurationAfterCheck: 1 * time.Millisecond,
   128  		},
   129  		KeepAlive: KeepAliveConfig{
   130  			SendInterval:    100 * time.Millisecond,
   131  			MaxSendErrCount: 5,
   132  		},
   133  	}
   134  	tc.fakeIPResolver = ip.NewResolverMock("ip")
   135  	tc.fakeLocationResolver = &mockLocationResolver{}
   136  	tc.statsReportInterval = 1 * time.Millisecond
   137  
   138  	brokerConn := nats.StartConnectionMock()
   139  	brokerConn.MockResponse("fake-node-1.p2p-config-exchange", []byte("123"))
   140  
   141  	tc.mockP2P = &mockP2PDialer{&mockP2PChannel{}}
   142  	tc.mockTime = time.Date(2000, time.January, 0, 10, 12, 3, 0, time.UTC)
   143  
   144  	tc.connManager = NewManager(
   145  		func(senderUUID string, channel p2p.Channel,
   146  			consumer, provider identity.Identity, hermes common.Address, proposal proposal.PricedServiceProposal, price market.Price,
   147  		) (PaymentIssuer, error) {
   148  			tc.MockPaymentIssuer = &MockPaymentIssuer{
   149  				stopChan: make(chan struct{}),
   150  			}
   151  			return tc.MockPaymentIssuer, nil
   152  		},
   153  		tc.fakeConnectionFactory.CreateConnection,
   154  		tc.stubPublisher,
   155  		tc.fakeIPResolver,
   156  		tc.fakeLocationResolver,
   157  		tc.config,
   158  		tc.statsReportInterval,
   159  		&mockValidator{},
   160  		tc.mockP2P,
   161  		func() {}, func() {},
   162  	)
   163  	tc.connManager.timeGetter = func() time.Time {
   164  		return tc.mockTime
   165  	}
   166  }
   167  
   168  func (tc *testContext) TestWhenNoConnectionIsMadeStatusIsNotConnected() {
   169  	assert.Exactly(tc.T(), connectionstate.Status{State: connectionstate.NotConnected}, tc.connManager.Status())
   170  }
   171  
   172  func (tc *testContext) TestOnConnectErrorStatusIsNotConnected() {
   173  	tc.fakeConnectionFactory.mockError = errors.New("fatal connection error")
   174  
   175  	assert.Error(tc.T(), tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{}))
   176  	assert.Equal(
   177  		tc.T(),
   178  		connectionstate.Status{
   179  			StartedAt:        tc.mockTime,
   180  			ConsumerID:       consumerID,
   181  			ConsumerLocation: consumerLocation,
   182  			HermesID:         hermesID,
   183  			State:            connectionstate.NotConnected,
   184  			Proposal:         activeProposal,
   185  		},
   186  		tc.connManager.Status(),
   187  	)
   188  }
   189  
   190  func (tc *testContext) TestWhenManagerMadeConnectionStatusReturnsConnectedStateAndSessionId() {
   191  	err := tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   192  	assert.NoError(tc.T(), err)
   193  	assert.Equal(
   194  		tc.T(),
   195  		connectionstate.Status{
   196  			StartedAt:        tc.mockTime,
   197  			ConsumerID:       consumerID,
   198  			ConsumerLocation: consumerLocation,
   199  			HermesID:         hermesID,
   200  			State:            connectionstate.Connected,
   201  			SessionID:        establishedSessionID,
   202  			Proposal:         activeProposal,
   203  		},
   204  		tc.connManager.Status(),
   205  	)
   206  }
   207  
   208  func (tc *testContext) TestStatusReportsConnectingWhenConnectionIsInProgress() {
   209  	tc.fakeConnectionFactory.mockConnection.onStartReportStates = []fakeState{}
   210  
   211  	go func() {
   212  		tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   213  	}()
   214  
   215  	waitABit()
   216  
   217  	assert.Equal(
   218  		tc.T(),
   219  		connectionstate.Status{
   220  			StartedAt:        tc.mockTime,
   221  			ConsumerID:       consumerID,
   222  			ConsumerLocation: consumerLocation,
   223  			HermesID:         hermesID,
   224  			State:            connectionstate.Connecting,
   225  			SessionID:        establishedSessionID,
   226  			Proposal:         activeProposal,
   227  		},
   228  		tc.connManager.Status(),
   229  	)
   230  	tc.connManager.Disconnect()
   231  }
   232  
   233  func (tc *testContext) TestStatusReportsNotConnected() {
   234  	tc.fakeConnectionFactory.mockConnection.onStopReportStates = []fakeState{}
   235  	tc.fakeConnectionFactory.mockConnection.stopBlock = make(chan struct{})
   236  	defer func() {
   237  		tc.fakeConnectionFactory.mockConnection.stopBlock = nil
   238  	}()
   239  
   240  	err := tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   241  	assert.NoError(tc.T(), err)
   242  	assert.Equal(tc.T(), connectionstate.Connected, tc.connManager.Status().State)
   243  
   244  	go func() {
   245  		assert.NoError(tc.T(), tc.connManager.Disconnect())
   246  	}()
   247  
   248  	waitABit()
   249  	assert.Equal(
   250  		tc.T(),
   251  		connectionstate.Status{
   252  			StartedAt:        tc.mockTime,
   253  			ConsumerID:       consumerID,
   254  			ConsumerLocation: consumerLocation,
   255  			HermesID:         hermesID,
   256  			State:            connectionstate.Disconnecting,
   257  			SessionID:        establishedSessionID,
   258  			Proposal:         activeProposal,
   259  		},
   260  		tc.connManager.Status(),
   261  	)
   262  
   263  	tc.fakeConnectionFactory.mockConnection.stopBlock <- struct{}{}
   264  
   265  	tc.fakeConnectionFactory.mockConnection.reportState(exitingState)
   266  	tc.fakeConnectionFactory.mockConnection.reportState(processExited)
   267  
   268  	waitABit()
   269  	assert.Equal(
   270  		tc.T(),
   271  		connectionstate.Status{
   272  			StartedAt:        tc.mockTime,
   273  			ConsumerID:       consumerID,
   274  			ConsumerLocation: consumerLocation,
   275  			HermesID:         hermesID,
   276  			State:            connectionstate.NotConnected,
   277  			SessionID:        establishedSessionID,
   278  			Proposal:         activeProposal,
   279  		},
   280  		tc.connManager.Status(),
   281  	)
   282  }
   283  
   284  func (tc *testContext) TestConnectResultsInAlreadyConnectedErrorWhenConnectionExists() {
   285  	assert.NoError(tc.T(), tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{}))
   286  	assert.Equal(tc.T(), ErrAlreadyExists, tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{}))
   287  }
   288  
   289  func (tc *testContext) TestDisconnectReturnsErrorWhenNoConnectionExists() {
   290  	assert.Equal(tc.T(), ErrNoConnection, tc.connManager.Disconnect())
   291  }
   292  
   293  func (tc *testContext) TestReconnectingStatusIsReportedWhenOpenVpnGoesIntoReconnectingState() {
   294  	assert.NoError(tc.T(), tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{}))
   295  	tc.fakeConnectionFactory.mockConnection.reportState(reconnectingState)
   296  	waitABit()
   297  	assert.Equal(
   298  		tc.T(),
   299  		connectionstate.Status{
   300  			StartedAt:        tc.mockTime,
   301  			ConsumerID:       consumerID,
   302  			ConsumerLocation: consumerLocation,
   303  			HermesID:         hermesID,
   304  			State:            connectionstate.Reconnecting,
   305  			SessionID:        establishedSessionID,
   306  			Proposal:         activeProposal,
   307  		},
   308  		tc.connManager.Status(),
   309  	)
   310  }
   311  
   312  func (tc *testContext) TestDoubleDisconnectResultsInError() {
   313  	assert.NoError(tc.T(), tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{}))
   314  	assert.Equal(tc.T(), connectionstate.Connected, tc.connManager.Status().State)
   315  	assert.NoError(tc.T(), tc.connManager.Disconnect())
   316  	waitABit()
   317  	assert.Equal(tc.T(), connectionstate.NotConnected, tc.connManager.Status().State)
   318  	assert.Equal(tc.T(), ErrNoConnection, tc.connManager.Disconnect())
   319  }
   320  
   321  func (tc *testContext) TestTwoConnectDisconnectCyclesReturnNoError() {
   322  	assert.NoError(tc.T(), tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{}))
   323  	assert.Equal(tc.T(), connectionstate.Connected, tc.connManager.Status().State)
   324  	assert.NoError(tc.T(), tc.connManager.Disconnect())
   325  	waitABit()
   326  	assert.Equal(tc.T(), connectionstate.NotConnected, tc.connManager.Status().State)
   327  
   328  	assert.NoError(tc.T(), tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{}))
   329  	assert.Equal(tc.T(), connectionstate.Connected, tc.connManager.Status().State)
   330  	assert.NoError(tc.T(), tc.connManager.Disconnect())
   331  	waitABit()
   332  	assert.Equal(tc.T(), connectionstate.NotConnected, tc.connManager.Status().State)
   333  }
   334  
   335  func (tc *testContext) TestConnectFailsIfConnectionFactoryReturnsError() {
   336  	tc.fakeConnectionFactory.mockError = errors.New("failed to create connection instance")
   337  	assert.Error(tc.T(), tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{}))
   338  }
   339  
   340  func (tc *testContext) TestStatusIsConnectedWhenConnectCommandReturnsWithoutError() {
   341  	tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   342  	assert.Equal(
   343  		tc.T(),
   344  		connectionstate.Status{
   345  			StartedAt:        tc.mockTime,
   346  			ConsumerID:       consumerID,
   347  			ConsumerLocation: consumerLocation,
   348  			HermesID:         hermesID,
   349  			State:            connectionstate.Connected,
   350  			SessionID:        establishedSessionID,
   351  			Proposal:         activeProposal,
   352  		},
   353  		tc.connManager.Status(),
   354  	)
   355  }
   356  
   357  func (tc *testContext) TestConnectingInProgressCanBeCanceled() {
   358  	tc.fakeConnectionFactory.mockConnection.onStartReportStates = []fakeState{}
   359  	tc.fakeConnectionFactory.mockConnection.onStopReportStates = []fakeState{}
   360  
   361  	connectWaiter := &sync.WaitGroup{}
   362  	connectWaiter.Add(1)
   363  	var err error
   364  	go func() {
   365  		defer connectWaiter.Done()
   366  		err = tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   367  	}()
   368  
   369  	waitABit()
   370  	assert.Equal(tc.T(), connectionstate.Connecting, tc.connManager.Status().State)
   371  	assert.NoError(tc.T(), tc.connManager.Disconnect())
   372  
   373  	connectWaiter.Wait()
   374  
   375  	assert.Equal(tc.T(), ErrConnectionCancelled, err)
   376  }
   377  
   378  func (tc *testContext) TestConnectMethodReturnsErrorIfConnectionExitsDuringConnect() {
   379  	tc.fakeConnectionFactory.mockConnection.onStartReportStates = []fakeState{}
   380  	tc.fakeConnectionFactory.mockConnection.onStopReportStates = []fakeState{}
   381  	connectWaiter := sync.WaitGroup{}
   382  	connectWaiter.Add(1)
   383  
   384  	var err error
   385  	go func() {
   386  		defer connectWaiter.Done()
   387  		err = tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   388  	}()
   389  	waitABit()
   390  	tc.fakeConnectionFactory.mockConnection.reportState(processExited)
   391  	connectWaiter.Wait()
   392  	assert.Equal(tc.T(), ErrConnectionFailed, err)
   393  }
   394  
   395  func (tc *testContext) Test_PaymentManager_WhenManagerMadeConnectionIsStarted() {
   396  	err := tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   397  	waitABit()
   398  	assert.NoError(tc.T(), err)
   399  	assert.True(tc.T(), tc.MockPaymentIssuer.StartCalled())
   400  }
   401  
   402  func (tc *testContext) Test_PaymentManager_OnConnectErrorIsStopped() {
   403  	tc.fakeConnectionFactory.mockConnection.onStartReturnError = errors.New("fatal connection error")
   404  	err := tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   405  	assert.Error(tc.T(), err)
   406  	assert.True(tc.T(), tc.MockPaymentIssuer.StopCalled())
   407  }
   408  
   409  func (tc *testContext) Test_SessionEndPublished_OnConnectError() {
   410  	tc.stubPublisher.Clear()
   411  
   412  	tc.fakeConnectionFactory.mockConnection.onStartReturnError = errors.New("fatal connection error")
   413  	err := tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   414  	assert.Error(tc.T(), err)
   415  
   416  	history := tc.stubPublisher.GetEventHistory()
   417  
   418  	found := false
   419  
   420  	for _, v := range history {
   421  		if v.Topic == connectionstate.AppTopicConnectionSession {
   422  			event := v.Event.(connectionstate.AppEventConnectionSession)
   423  			if event.Status == connectionstate.SessionEndedStatus {
   424  				found = true
   425  
   426  				assert.Equal(tc.T(), connectionstate.SessionEndedStatus, event.Status)
   427  				assert.Equal(tc.T(), consumerID, event.SessionInfo.ConsumerID)
   428  				assert.Equal(tc.T(), establishedSessionID, event.SessionInfo.SessionID)
   429  				assert.Equal(tc.T(), activeProposal.ProviderID, event.SessionInfo.Proposal.ProviderID)
   430  				assert.Equal(tc.T(), activeProposal.ServiceType, event.SessionInfo.Proposal.ServiceType)
   431  			}
   432  		}
   433  	}
   434  
   435  	assert.True(tc.T(), found)
   436  }
   437  
   438  func (tc *testContext) Test_ManagerPublishesEvents() {
   439  	tc.stubPublisher.Clear()
   440  
   441  	tc.fakeConnectionFactory.mockConnection.onStartReportStates = []fakeState{
   442  		connectedState,
   443  	}
   444  
   445  	err := tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   446  	assert.NoError(tc.T(), err)
   447  
   448  	waitABit()
   449  
   450  	history := tc.stubPublisher.GetEventHistory()
   451  	assert.True(tc.T(), len(history) >= 4)
   452  
   453  	// Check if published to all expected topics.
   454  	expectedTopics := [...]string{connectionstate.AppTopicConnectionStatistics, connectionstate.AppTopicConnectionState, connectionstate.AppTopicConnectionSession}
   455  	for _, v := range expectedTopics {
   456  		var published bool
   457  		for _, h := range history {
   458  			if v == h.Topic {
   459  				published = true
   460  			}
   461  		}
   462  		tc.Assert().Truef(published, "expected publish event to %s", v)
   463  	}
   464  
   465  	// Check received events data.
   466  	for _, v := range history {
   467  		if v.Topic == connectionstate.AppTopicConnectionStatistics {
   468  			event := v.Event.(connectionstate.AppEventConnectionStatistics)
   469  			assert.True(tc.T(), event.Stats.BytesReceived == tc.mockStatistics.BytesReceived)
   470  			assert.True(tc.T(), event.Stats.BytesSent == tc.mockStatistics.BytesSent)
   471  		}
   472  		if v.Topic == connectionstate.AppTopicConnectionState && v.Event.(connectionstate.AppEventConnectionState).State == connectionstate.Connected {
   473  			event := v.Event.(connectionstate.AppEventConnectionState)
   474  			assert.Equal(tc.T(), connectionstate.Connected, event.State)
   475  			assert.Equal(tc.T(), consumerID, event.SessionInfo.ConsumerID)
   476  			assert.Equal(tc.T(), establishedSessionID, event.SessionInfo.SessionID)
   477  			assert.Equal(tc.T(), activeProposal.ProviderID, event.SessionInfo.Proposal.ProviderID)
   478  			assert.Equal(tc.T(), activeProposal.ServiceType, event.SessionInfo.Proposal.ServiceType)
   479  		}
   480  		if v.Topic == connectionstate.AppTopicConnectionState && v.Event.(connectionstate.AppEventConnectionState).State == connectionstate.StateIPNotChanged {
   481  			event := v.Event.(connectionstate.AppEventConnectionState)
   482  			assert.Equal(tc.T(), connectionstate.StateIPNotChanged, event.State)
   483  			assert.Equal(tc.T(), consumerID, event.SessionInfo.ConsumerID)
   484  			assert.Equal(tc.T(), establishedSessionID, event.SessionInfo.SessionID)
   485  			assert.Equal(tc.T(), activeProposal.ProviderID, event.SessionInfo.Proposal.ProviderID)
   486  			assert.Equal(tc.T(), activeProposal.ServiceType, event.SessionInfo.Proposal.ServiceType)
   487  		}
   488  		if v.Topic == connectionstate.AppTopicConnectionSession {
   489  			event := v.Event.(connectionstate.AppEventConnectionSession)
   490  			assert.Equal(tc.T(), connectionstate.SessionCreatedStatus, event.Status)
   491  			assert.Equal(tc.T(), consumerID, event.SessionInfo.ConsumerID)
   492  			assert.Equal(tc.T(), establishedSessionID, event.SessionInfo.SessionID)
   493  			assert.Equal(tc.T(), activeProposal.ProviderID, event.SessionInfo.Proposal.ProviderID)
   494  			assert.Equal(tc.T(), activeProposal.ServiceType, event.SessionInfo.Proposal.ServiceType)
   495  		}
   496  	}
   497  }
   498  
   499  func (tc *testContext) Test_ManagerNotifiesAboutSessionIPNotChanged() {
   500  	tc.stubPublisher.Clear()
   501  
   502  	tc.fakeConnectionFactory.mockConnection.onStartReportStates = []fakeState{
   503  		connectedState,
   504  	}
   505  
   506  	err := tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   507  	assert.NoError(tc.T(), err)
   508  
   509  	assert.Eventually(tc.T(), func() bool {
   510  		// Check that state event with StateIPNotChanged status was called.
   511  		history := tc.stubPublisher.GetEventHistory()
   512  		for _, v := range history {
   513  			if v.Topic == connectionstate.AppTopicConnectionState && v.Event.(connectionstate.AppEventConnectionState).State == connectionstate.StateIPNotChanged {
   514  				return true
   515  			}
   516  		}
   517  		return false
   518  	}, 2*time.Second, 10*time.Millisecond)
   519  
   520  	// Check that status sender was called with status code.
   521  	expectedStatusMsg := &pb.SessionStatus{
   522  		ConsumerID: consumerID.Address,
   523  		SessionID:  string(establishedSessionID),
   524  		Code:       uint32(connectivity.StatusSessionIPNotChanged),
   525  		Message:    "",
   526  	}
   527  	assert.True(
   528  		tc.T(),
   529  		proto.Equal(expectedStatusMsg, tc.mockP2P.ch.getSentMsg()),
   530  		fmt.Sprintf("Session status are not equal:\nexpected: %v\nactual:  %v", expectedStatusMsg, tc.mockP2P.ch.getSentMsg()),
   531  	)
   532  }
   533  
   534  func (tc *testContext) Test_ManagerNotifiesAboutSuccessfulConnection() {
   535  	tc.stubPublisher.Clear()
   536  
   537  	tc.fakeConnectionFactory.mockConnection.onStartReportStates = []fakeState{
   538  		connectedState,
   539  	}
   540  
   541  	// Simulate IP change.
   542  	tc.connManager.ipResolver = ip.NewResolverMockMultiple("127.0.0.1", "10.0.0.4", "10.0.5")
   543  
   544  	err := tc.connManager.Connect(consumerID, hermesID, activeProposalLookup, ConnectParams{})
   545  	assert.NoError(tc.T(), err)
   546  
   547  	waitABit()
   548  
   549  	// Check that state event with StateIPNotChanged status was not called.
   550  	history := tc.stubPublisher.GetEventHistory()
   551  	var ipNotChangedEvent *mocks.EventBusEntry
   552  	for _, v := range history {
   553  		if v.Topic == connectionstate.AppTopicConnectionState && v.Event.(connectionstate.AppEventConnectionState).State == connectionstate.StateIPNotChanged {
   554  			ipNotChangedEvent = &v
   555  		}
   556  	}
   557  	assert.Nil(tc.T(), ipNotChangedEvent)
   558  
   559  	// Check that status sender was called with status code.
   560  	expectedStatusMsg := &pb.SessionStatus{
   561  		ConsumerID: consumerID.Address,
   562  		SessionID:  string(establishedSessionID),
   563  		Code:       uint32(connectivity.StatusConnectionOk),
   564  		Message:    "",
   565  	}
   566  	assert.True(
   567  		tc.T(),
   568  		proto.Equal(expectedStatusMsg, tc.mockP2P.ch.getSentMsg()),
   569  		fmt.Sprintf("Session status are not equal:\nexpected: %v\nactual:  %v", expectedStatusMsg, tc.mockP2P.ch.getSentMsg()),
   570  	)
   571  }
   572  
   573  func TestConnectionManagerSuite(t *testing.T) {
   574  	suite.Run(t, new(testContext))
   575  }
   576  
   577  func waitABit() {
   578  	// usually time.Sleep call gives a chance for other goroutines to kick in
   579  	// important when testing async code
   580  	time.Sleep(10 * time.Millisecond)
   581  }
   582  
   583  type MockPaymentIssuer struct {
   584  	startCalled bool
   585  	stopCalled  bool
   586  	MockError   error
   587  	stopChan    chan struct{}
   588  	sync.Mutex
   589  }
   590  
   591  func (mpm *MockPaymentIssuer) Start() error {
   592  	mpm.Lock()
   593  	mpm.startCalled = true
   594  	mpm.Unlock()
   595  	<-mpm.stopChan
   596  	return mpm.MockError
   597  }
   598  
   599  func (mpm *MockPaymentIssuer) StartCalled() bool {
   600  	mpm.Lock()
   601  	defer mpm.Unlock()
   602  	return mpm.startCalled
   603  }
   604  
   605  func (mpm *MockPaymentIssuer) StopCalled() bool {
   606  	mpm.Lock()
   607  	defer mpm.Unlock()
   608  	return mpm.stopCalled
   609  }
   610  
   611  func (mpm *MockPaymentIssuer) Stop() {
   612  	mpm.Lock()
   613  	defer mpm.Unlock()
   614  	mpm.stopCalled = true
   615  	close(mpm.stopChan)
   616  }
   617  
   618  func (mpm *MockPaymentIssuer) SetSessionID(string) {
   619  }
   620  
   621  type mockP2PDialer struct {
   622  	ch *mockP2PChannel
   623  }
   624  
   625  func (m mockP2PDialer) Dial(ctx context.Context, consumerID identity.Identity, providerID identity.Identity, serviceType string, contactDef p2p.ContactDefinition, tracer *trace.Tracer) (p2p.Channel, error) {
   626  	return m.ch, nil
   627  }
   628  
   629  type mockP2PChannel struct {
   630  	status proto.Message
   631  	lock   sync.Mutex
   632  }
   633  
   634  func (m *mockP2PChannel) Conn() *net.UDPConn {
   635  	return &net.UDPConn{}
   636  }
   637  
   638  func (m *mockP2PChannel) getSentMsg() proto.Message {
   639  	m.lock.Lock()
   640  	defer m.lock.Unlock()
   641  	return m.status
   642  }
   643  
   644  func (m *mockP2PChannel) Send(_ context.Context, topic string, msg *p2p.Message) (*p2p.Message, error) {
   645  	switch topic {
   646  	case p2p.TopicSessionCreate:
   647  		res := &pb.SessionResponse{
   648  			ID: string(establishedSessionID),
   649  		}
   650  		return p2p.ProtoMessage(res), nil
   651  	case p2p.TopicSessionStatus:
   652  		m.lock.Lock()
   653  		m.status = &pb.SessionStatus{}
   654  		msg.UnmarshalProto(m.status)
   655  		m.lock.Unlock()
   656  
   657  		return nil, nil
   658  	case p2p.TopicSessionAcknowledge:
   659  		return nil, nil
   660  	}
   661  
   662  	return nil, errors.New("unexpected error")
   663  }
   664  
   665  func (m *mockP2PChannel) Handle(topic string, handler p2p.HandlerFunc) {
   666  }
   667  
   668  func (m *mockP2PChannel) Tracer() *trace.Tracer {
   669  	return nil
   670  }
   671  
   672  func (m *mockP2PChannel) ServiceConn() *net.UDPConn {
   673  	raddr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:12345")
   674  	conn, _ := net.DialUDP("udp", nil, raddr)
   675  	return conn
   676  }
   677  
   678  func (m *mockP2PChannel) Close() error {
   679  	return nil
   680  }
   681  
   682  func (m *mockP2PChannel) ID() string {
   683  	return fmt.Sprintf("%p", m)
   684  }
   685  
   686  type mockValidator struct {
   687  	errorToReturn error
   688  }
   689  
   690  func (mv *mockValidator) Validate(chainID int64, consumerID identity.Identity, price market.Price) error {
   691  	return mv.errorToReturn
   692  }
   693  
   694  type mockLocationResolver struct{}
   695  
   696  func (mlr *mockLocationResolver) GetOrigin() locationstate.Location {
   697  	return consumerLocation
   698  }