mirror of
https://github.com/status-im/status-go.git
synced 2025-01-18 18:55:47 +00:00
2d964bfe9f
The main goal of this change is to remove async operations from node manager. Additionally all of the signals from node manager are moved to status backend. All of the async operation now will have the following behaviour: - If node in the correct state exit immediatly without error - If node not in the correct state exit immediatly with error - In all other cases spawn a goroutine with wanted operation - All the progress regarding that operation will be reported by using signals - Signals should be handled in once place, which is StatusBackend There are 2 potentially breaking changes: - Empty event field will be ommited when Envelope is sent to a client - All errors will be delivered to a client as an Envelope, previously some errors (NodeExists, NoRunningNode) were delivered synchronously Signed-off-by: Dmitry Shulyak <yashulyak@gmail.com>
245 lines
6.1 KiB
Go
245 lines
6.1 KiB
Go
package api_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"math/rand"
|
|
"os"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/status-im/status-go/geth/api"
|
|
"github.com/status-im/status-go/geth/log"
|
|
"github.com/status-im/status-go/geth/node"
|
|
"github.com/status-im/status-go/geth/params"
|
|
"github.com/status-im/status-go/geth/signal"
|
|
e2e "github.com/status-im/status-go/t/e2e"
|
|
. "github.com/status-im/status-go/t/utils"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
const (
|
|
testChatID = "testChat"
|
|
)
|
|
|
|
func TestAPI(t *testing.T) {
|
|
suite.Run(t, new(APITestSuite))
|
|
}
|
|
|
|
type APITestSuite struct {
|
|
suite.Suite
|
|
api *api.StatusAPI
|
|
}
|
|
|
|
func (s *APITestSuite) ensureNodeStopped() {
|
|
if err := s.api.StopNode(); err != node.ErrNoRunningNode && err != nil {
|
|
s.NoError(err, "unexpected error")
|
|
}
|
|
}
|
|
|
|
func (s *APITestSuite) SetupTest() {
|
|
s.api = api.NewStatusAPI()
|
|
s.NotNil(s.api)
|
|
}
|
|
|
|
func (s *APITestSuite) TestCHTUpdate() {
|
|
tmpDir, err := ioutil.TempDir(os.TempDir(), "cht-updates")
|
|
s.NoError(err)
|
|
defer os.RemoveAll(tmpDir) //nolint: errcheck
|
|
|
|
configJSON := `{
|
|
"NetworkId": ` + strconv.Itoa(params.RopstenNetworkID) + `,
|
|
"DataDir": "` + tmpDir + `",
|
|
"LogLevel": "INFO",
|
|
"RPCEnabled": true
|
|
}`
|
|
|
|
_, err = params.LoadNodeConfig(configJSON)
|
|
s.NoError(err)
|
|
// TODO(tiabc): Test that CHT is really updated.
|
|
}
|
|
|
|
func (s *APITestSuite) TestRaceConditions() {
|
|
cnt := 25
|
|
progress := make(chan struct{}, cnt)
|
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
nodeConfig1, err := e2e.MakeTestNodeConfig(GetNetworkID())
|
|
s.NoError(err)
|
|
|
|
nodeConfig2, err := e2e.MakeTestNodeConfig(GetNetworkID())
|
|
s.NoError(err)
|
|
|
|
nodeConfigs := []*params.NodeConfig{nodeConfig1, nodeConfig2}
|
|
|
|
var funcsToTest = []func(*params.NodeConfig){
|
|
func(config *params.NodeConfig) {
|
|
log.Info("StartNodeAsync()")
|
|
s.api.StartNodeAsync(config)
|
|
s.T().Logf("StartNodeAsync() for network: %d", config.NetworkID)
|
|
progress <- struct{}{}
|
|
},
|
|
func(config *params.NodeConfig) {
|
|
log.Info("StopNodeAsync()")
|
|
s.api.StopNodeAsync()
|
|
s.T().Logf("StopNodeAsync()")
|
|
progress <- struct{}{}
|
|
},
|
|
func(config *params.NodeConfig) {
|
|
log.Info("RestartNodeAsync()")
|
|
s.api.RestartNodeAsync()
|
|
s.T().Logf("RestartNodeAsync()")
|
|
progress <- struct{}{}
|
|
},
|
|
// TODO(adam): quarantined until it uses a different datadir
|
|
// as otherwise it wipes out cached blockchain data.
|
|
// func(config *params.NodeConfig) {
|
|
// log.Info("ResetChainDataAsync()")
|
|
// _, err := s.api.ResetChainDataAsync()
|
|
// s.T().Logf("ResetChainDataAsync(), error: %v", err)
|
|
// progress <- struct{}{}
|
|
// },
|
|
}
|
|
|
|
// increase StartNode()/StopNode() population
|
|
for i := 0; i < 5; i++ {
|
|
funcsToTest = append(funcsToTest, funcsToTest[0], funcsToTest[1])
|
|
}
|
|
|
|
for i := 0; i < cnt; i++ {
|
|
randConfig := nodeConfigs[rnd.Intn(len(nodeConfigs))]
|
|
randFunc := funcsToTest[rnd.Intn(len(funcsToTest))]
|
|
|
|
if rnd.Intn(100) > 75 { // introduce random delays
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
go randFunc(randConfig)
|
|
}
|
|
|
|
for range progress {
|
|
cnt -= 1
|
|
if cnt <= 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
time.Sleep(2 * time.Second) // so that we see some logs
|
|
// just in case we have a node running
|
|
s.ensureNodeStopped()
|
|
}
|
|
|
|
func (s *APITestSuite) TestCellsRemovedAfterSwitchAccount() {
|
|
const itersCount = 5
|
|
var (
|
|
require = s.Require()
|
|
getChatId = func(id int) string {
|
|
return testChatID + strconv.Itoa(id)
|
|
}
|
|
)
|
|
|
|
config, err := e2e.MakeTestNodeConfig(GetNetworkID())
|
|
require.NoError(err)
|
|
err = s.api.StartNode(config)
|
|
require.NoError(err)
|
|
defer s.api.StopNode() //nolint: errcheck
|
|
|
|
address1, _, _, err := s.api.AccountManager().CreateAccount(TestConfig.Account1.Password)
|
|
require.NoError(err)
|
|
|
|
address2, _, _, err := s.api.AccountManager().CreateAccount(TestConfig.Account2.Password)
|
|
require.NoError(err)
|
|
|
|
err = s.api.SelectAccount(address1, TestConfig.Account1.Password)
|
|
require.NoError(err)
|
|
|
|
for i := 0; i < itersCount; i++ {
|
|
_, e := s.api.JailManager().CreateCell(getChatId(i))
|
|
require.NoError(e)
|
|
}
|
|
|
|
err = s.api.SelectAccount(address2, TestConfig.Account2.Password)
|
|
require.NoError(err)
|
|
|
|
for i := 0; i < itersCount; i++ {
|
|
_, e := s.api.JailManager().Cell(getChatId(i))
|
|
require.Error(e)
|
|
}
|
|
}
|
|
|
|
// TestLogoutRemovesCells we want be sure that
|
|
// cells will be removed after the API call "Logout"
|
|
func (s *APITestSuite) TestLogoutRemovesCells() {
|
|
var (
|
|
err error
|
|
require = s.Require()
|
|
)
|
|
|
|
config, err := e2e.MakeTestNodeConfig(GetNetworkID())
|
|
require.NoError(err)
|
|
err = s.api.StartNode(config)
|
|
require.NoError(err)
|
|
defer s.api.StopNode() //nolint: errcheck
|
|
|
|
address1, _, _, err := s.api.AccountManager().CreateAccount(TestConfig.Account1.Password)
|
|
require.NoError(err)
|
|
|
|
err = s.api.SelectAccount(address1, TestConfig.Account1.Password)
|
|
require.NoError(err)
|
|
|
|
s.api.JailManager().CreateAndInitCell(testChatID)
|
|
|
|
err = s.api.Logout()
|
|
require.NoError(err)
|
|
|
|
_, err = s.api.JailManager().Cell(testChatID)
|
|
require.Error(err, "Expected that cells was removed")
|
|
}
|
|
|
|
func (s *APITestSuite) TestNodeStartCrash() {
|
|
// let's listen for node.crashed signal
|
|
signalReceived := make(chan struct{})
|
|
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var envelope signal.Envelope
|
|
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
|
s.NoError(err)
|
|
|
|
if envelope.Type == signal.EventNodeCrashed {
|
|
close(signalReceived)
|
|
}
|
|
})
|
|
defer signal.ResetDefaultNodeNotificationHandler()
|
|
|
|
nodeConfig, err := e2e.MakeTestNodeConfig(GetNetworkID())
|
|
s.NoError(err)
|
|
|
|
// start node outside the manager (on the same port), so that manager node.Start() method fails
|
|
outsideNode, err := node.MakeNode(nodeConfig)
|
|
s.NoError(err)
|
|
err = outsideNode.Start()
|
|
s.NoError(err)
|
|
|
|
// now try starting using node manager, it should fail (error is irrelevant as it is implementation detail)
|
|
s.Error(<-s.api.StartNodeAsync(nodeConfig))
|
|
|
|
select {
|
|
case <-time.After(500 * time.Millisecond):
|
|
s.FailNow("timed out waiting for signal")
|
|
case <-signalReceived:
|
|
}
|
|
|
|
// stop outside node, and re-try
|
|
s.NoError(outsideNode.Stop())
|
|
signalReceived = make(chan struct{})
|
|
s.NoError(<-s.api.StartNodeAsync(nodeConfig))
|
|
|
|
select {
|
|
case <-time.After(500 * time.Millisecond):
|
|
case <-signalReceived:
|
|
s.FailNow("signal should not be received")
|
|
}
|
|
|
|
// cleanup
|
|
s.NoError(s.api.StopNode())
|
|
}
|