From 9de9bcb17c7b2ea90670b526356a17af8995b2ee Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 31 Jan 2022 16:20:14 +0300 Subject: [PATCH 1/7] cli: add tests for server commands Fixes: * Proper exit code should be returned on exit for server-related commands --- cli/dump_test.go | 64 +++++++++++++++++--- cli/executor_test.go | 58 +++++++++++++++++- cli/server/server.go | 17 +++--- cli/server/server_test.go | 122 ++++++++++++++++++++++++++++++++++++- cli/server_test.go | 124 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 364 insertions(+), 21 deletions(-) create mode 100644 cli/server_test.go diff --git a/cli/dump_test.go b/cli/dump_test.go index ae2719fff..63cc84936 100644 --- a/cli/dump_test.go +++ b/cli/dump_test.go @@ -4,6 +4,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strconv" "testing" "github.com/nspcc-dev/neo-go/pkg/config" @@ -11,15 +12,19 @@ import ( "gopkg.in/yaml.v2" ) -func TestDBRestore(t *testing.T) { +func TestDBRestoreDump(t *testing.T) { tmpDir := t.TempDir() - chainPath := filepath.Join(tmpDir, "neogotestchain") - cfg, err := config.LoadFile(filepath.Join("..", "config", "protocol.unit_testnet.yml")) - require.NoError(t, err, "could not load config") - cfg.ApplicationConfiguration.DBConfiguration.Type = "leveldb" - cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath = chainPath + loadConfig := func(t *testing.T) config.Config { + chainPath := filepath.Join(tmpDir, "neogotestchain") + cfg, err := config.LoadFile(filepath.Join("..", "config", "protocol.unit_testnet.yml")) + require.NoError(t, err, "could not load config") + cfg.ApplicationConfiguration.DBConfiguration.Type = "leveldb" + cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath = chainPath + return cfg + } + cfg := loadConfig(t) out, err := yaml.Marshal(cfg) require.NoError(t, err) @@ -29,6 +34,7 @@ func TestDBRestore(t *testing.T) { // generated via `go run ./scripts/gendump/main.go --out ./cli/testdata/chain50x2.acc --blocks 50 --txs 2` const inDump = "./testdata/chain50x2.acc" e := newExecutor(t, false) + stateDump := filepath.Join(tmpDir, "neogo.teststate") baseArgs := []string{"neo-go", "db", "restore", "--unittest", "--config-path", tmpDir, "--in", inDump, "--dump", stateDump} @@ -47,8 +53,50 @@ func TestDBRestore(t *testing.T) { // Dump and compare. dumpPath := filepath.Join(tmpDir, "testdump.acc") - e.Run(t, "neo-go", "db", "dump", "--unittest", - "--config-path", tmpDir, "--out", dumpPath) + + t.Run("missing config", func(t *testing.T) { + e.RunWithError(t, "neo-go", "db", "dump", "--privnet", + "--config-path", tmpDir, "--out", dumpPath) + }) + t.Run("bad logger config", func(t *testing.T) { + badConfigDir := t.TempDir() + logfile := filepath.Join(badConfigDir, "logdir") + require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + cfg = loadConfig(t) + cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log") + out, err = yaml.Marshal(cfg) + require.NoError(t, err) + + cfgPath = filepath.Join(badConfigDir, "protocol.unit_testnet.yml") + require.NoError(t, ioutil.WriteFile(cfgPath, out, os.ModePerm)) + + e.RunWithError(t, "neo-go", "db", "dump", "--unittest", + "--config-path", badConfigDir, "--out", dumpPath) + }) + t.Run("bad storage config", func(t *testing.T) { + badConfigDir := t.TempDir() + logfile := filepath.Join(badConfigDir, "logdir") + require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + cfg = loadConfig(t) + cfg.ApplicationConfiguration.DBConfiguration.Type = "" + out, err = yaml.Marshal(cfg) + require.NoError(t, err) + + cfgPath = filepath.Join(badConfigDir, "protocol.unit_testnet.yml") + require.NoError(t, ioutil.WriteFile(cfgPath, out, os.ModePerm)) + + e.RunWithError(t, "neo-go", "db", "dump", "--unittest", + "--config-path", badConfigDir, "--out", dumpPath) + }) + + baseCmd := []string{"neo-go", "db", "dump", "--unittest", + "--config-path", tmpDir, "--out", dumpPath} + + t.Run("invalid start/count", func(t *testing.T) { + e.RunWithError(t, append(baseCmd, "--start", "5", "--count", strconv.Itoa(50-5+1+1))...) + }) + + e.Run(t, baseCmd...) d1, err := ioutil.ReadFile(inDump) require.NoError(t, err) diff --git a/cli/executor_test.go b/cli/executor_test.go index 5ff7b2996..a82d8fc32 100644 --- a/cli/executor_test.go +++ b/cli/executor_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "math" "strings" + "sync" "testing" "time" @@ -58,13 +59,66 @@ type executor struct { // NetSrv is a network server (can be empty). NetSrv *network.Server // Out contains command output. - Out *bytes.Buffer + Out *ConcurrentBuffer // Err contains command errors. Err *bytes.Buffer // In contains command input. In *bytes.Buffer } +// ConcurrentBuffer is a wrapper over Buffer with mutex. +type ConcurrentBuffer struct { + lock sync.RWMutex + buf *bytes.Buffer +} + +// NewConcurrentBuffer returns new ConcurrentBuffer with underlying buffer initialized. +func NewConcurrentBuffer() *ConcurrentBuffer { + return &ConcurrentBuffer{ + buf: bytes.NewBuffer(nil), + } +} + +// Write is a concurrent wrapper over the corresponding method of bytes.Buffer. +func (w *ConcurrentBuffer) Write(p []byte) (int, error) { + w.lock.Lock() + defer w.lock.Unlock() + + return w.buf.Write(p) +} + +// ReadString is a concurrent wrapper over the corresponding method of bytes.Buffer. +func (w *ConcurrentBuffer) ReadString(delim byte) (string, error) { + w.lock.RLock() + defer w.lock.RUnlock() + + return w.buf.ReadString(delim) +} + +// Bytes is a concurrent wrapper over the corresponding method of bytes.Buffer. +func (w *ConcurrentBuffer) Bytes() []byte { + w.lock.RLock() + defer w.lock.RUnlock() + + return w.buf.Bytes() +} + +// String is a concurrent wrapper over the corresponding method of bytes.Buffer. +func (w *ConcurrentBuffer) String() string { + w.lock.RLock() + defer w.lock.RUnlock() + + return w.buf.String() +} + +// Reset is a concurrent wrapper over the corresponding method of bytes.Buffer. +func (w *ConcurrentBuffer) Reset() { + w.lock.Lock() + defer w.lock.Unlock() + + w.buf.Reset() +} + func newTestChain(t *testing.T, f func(*config.Config), run bool) (*core.Blockchain, *server.Server, *network.Server) { configPath := "../config/protocol.unit_testnet.single.yml" cfg, err := config.LoadFile(configPath) @@ -115,7 +169,7 @@ func newExecutorSuspended(t *testing.T) *executor { func newExecutorWithConfig(t *testing.T, needChain, runChain bool, f func(*config.Config)) *executor { e := &executor{ CLI: newApp(), - Out: bytes.NewBuffer(nil), + Out: NewConcurrentBuffer(), Err: bytes.NewBuffer(nil), In: bytes.NewBuffer(nil), } diff --git a/cli/server/server.go b/cli/server/server.go index 21156cec8..19d3a407a 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -393,11 +393,11 @@ func mkP2PNotary(config network.ServerConfig, chain *core.Blockchain, serv *netw func startServer(ctx *cli.Context) error { cfg, err := getConfigFromContext(ctx) if err != nil { - return err + return cli.NewExitError(err, 1) } log, err := handleLoggingParams(ctx, cfg.ApplicationConfiguration) if err != nil { - return err + return cli.NewExitError(err, 1) } grace, cancel := context.WithCancel(newGraceContext()) @@ -407,7 +407,7 @@ func startServer(ctx *cli.Context) error { chain, prometheus, pprof, err := initBCWithMetrics(cfg, log) if err != nil { - return err + return cli.NewExitError(err, 1) } serv, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), log) @@ -423,15 +423,15 @@ func startServer(ctx *cli.Context) error { oracleSrv, err := mkOracle(serverConfig, chain, serv, log) if err != nil { - return err + return cli.NewExitError(err, 1) } _, err = mkConsensus(serverConfig, chain, serv, log) if err != nil { - return err + return cli.NewExitError(err, 1) } _, err = mkP2PNotary(serverConfig, chain, serv, log) if err != nil { - return err + return cli.NewExitError(err, 1) } rpcServer := server.New(chain, cfg.ApplicationConfiguration.RPC, serv, oracleSrv, log) errChan := make(chan error) @@ -442,7 +442,7 @@ func startServer(ctx *cli.Context) error { sighupCh := make(chan os.Signal, 1) signal.Notify(sighupCh, syscall.SIGHUP) - fmt.Fprintln(ctx.App.Writer, logo()) + fmt.Fprintln(ctx.App.Writer, Logo()) fmt.Fprintln(ctx.App.Writer, serv.UserAgent) fmt.Fprintln(ctx.App.Writer) @@ -517,7 +517,8 @@ func initBlockChain(cfg config.Config, log *zap.Logger) (*core.Blockchain, error return chain, nil } -func logo() string { +// Logo returns Neo-Go logo. +func Logo() string { return ` _ ____________ __________ / | / / ____/ __ \ / ____/ __ \ diff --git a/cli/server/server_test.go b/cli/server/server_test.go index 454076c75..8046a457f 100644 --- a/cli/server/server_test.go +++ b/cli/server/server_test.go @@ -1,7 +1,9 @@ package server import ( + "encoding/binary" "flag" + "io/ioutil" "os" "path/filepath" "testing" @@ -14,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "github.com/urfave/cli" "go.uber.org/zap" + "gopkg.in/yaml.v2" ) // serverTestWD is the default working directory for server tests. @@ -42,6 +45,18 @@ func TestHandleLoggingParams(t *testing.T) { d := t.TempDir() testLog := filepath.Join(d, "file.log") + t.Run("logdir is a file", func(t *testing.T) { + logfile := filepath.Join(d, "logdir") + require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + ctx := cli.NewContext(cli.NewApp(), set, nil) + cfg := config.ApplicationConfiguration{ + LogPath: filepath.Join(logfile, "file.log"), + } + _, err := handleLoggingParams(ctx, cfg) + require.Error(t, err) + }) + t.Run("default", func(t *testing.T) { set := flag.NewFlagSet("flagSet", flag.ExitOnError) ctx := cli.NewContext(cli.NewApp(), set, nil) @@ -83,6 +98,12 @@ func TestInitBCWithMetrics(t *testing.T) { require.NoError(t, err) logger, err := handleLoggingParams(ctx, cfg.ApplicationConfiguration) require.NoError(t, err) + + t.Run("bad store", func(t *testing.T) { + _, _, _, err = initBCWithMetrics(config.Config{}, logger) + require.Error(t, err) + }) + chain, prometheus, pprof, err := initBCWithMetrics(cfg, logger) require.NoError(t, err) t.Cleanup(func() { @@ -139,9 +160,10 @@ func TestRestoreDB(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) }) - //dump first + // dump first set := flag.NewFlagSet("flagSet", flag.ExitOnError) - set.String("config-path", filepath.Join(serverTestWD, "..", "..", "config"), "") + goodCfg := filepath.Join(serverTestWD, "..", "..", "config") + cfgPath := set.String("config-path", goodCfg, "") set.Bool("privnet", true, "") set.Bool("debug", true, "") set.Int("start", 0, "") @@ -152,7 +174,101 @@ func TestRestoreDB(t *testing.T) { require.NoError(t, err) // and then restore - set.String("in", testDump, "") + t.Run("invalid config", func(t *testing.T) { + *cfgPath = filepath.Join(serverTestWD, "..", "..", "config_invalid") + require.Error(t, restoreDB(ctx)) + }) + t.Run("invalid logger path", func(t *testing.T) { + badCfgDir := t.TempDir() + logfile := filepath.Join(badCfgDir, "logdir") + require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + cfg, err := config.LoadFile(filepath.Join(goodCfg, "protocol.privnet.yml")) + require.NoError(t, err, "could not load config") + cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log") + out, err := yaml.Marshal(cfg) + require.NoError(t, err) + + badCfgPath := filepath.Join(badCfgDir, "protocol.privnet.yml") + require.NoError(t, ioutil.WriteFile(badCfgPath, out, os.ModePerm)) + + *cfgPath = badCfgDir + require.Error(t, restoreDB(ctx)) + + *cfgPath = goodCfg + }) + t.Run("invalid bc config", func(t *testing.T) { + badCfgDir := t.TempDir() + cfg, err := config.LoadFile(filepath.Join(goodCfg, "protocol.privnet.yml")) + require.NoError(t, err, "could not load config") + cfg.ApplicationConfiguration.DBConfiguration.Type = "" + out, err := yaml.Marshal(cfg) + require.NoError(t, err) + + badCfgPath := filepath.Join(badCfgDir, "protocol.privnet.yml") + require.NoError(t, ioutil.WriteFile(badCfgPath, out, os.ModePerm)) + + *cfgPath = badCfgDir + require.Error(t, restoreDB(ctx)) + + *cfgPath = goodCfg + }) + + in := set.String("in", testDump, "") + incremental := set.Bool("incremental", false, "") + t.Run("invalid in", func(t *testing.T) { + *in = "unknown-file" + require.Error(t, restoreDB(ctx)) + + *in = testDump + }) + t.Run("corrupted in: invalid block count", func(t *testing.T) { + inPath := filepath.Join(t.TempDir(), "file3.acc") + require.NoError(t, os.WriteFile(inPath, []byte{1, 2, 3}, // file is expected to start from uint32 + os.ModePerm)) + *in = inPath + require.Error(t, restoreDB(ctx)) + + *in = testDump + }) + t.Run("corrupted in: corrupted block", func(t *testing.T) { + inPath := filepath.Join(t.TempDir(), "file3.acc") + b, err := os.ReadFile(testDump) + require.NoError(t, err) + b[5] = 0xff // file is expected to start from uint32 (4 bytes) followed by the first block, so corrupt the first block bytes + require.NoError(t, os.WriteFile(inPath, b, os.ModePerm)) + *in = inPath + require.Error(t, restoreDB(ctx)) + + *in = testDump + }) + t.Run("incremental dump", func(t *testing.T) { + inPath := filepath.Join(t.TempDir(), "file1_incremental.acc") + b, err := os.ReadFile(testDump) + require.NoError(t, err) + start := make([]byte, 4) + t.Run("good", func(t *testing.T) { + binary.LittleEndian.PutUint32(start, 1) // start from the first block + require.NoError(t, os.WriteFile(inPath, append(start, b...), + os.ModePerm)) + *in = inPath + *incremental = true + + require.NoError(t, restoreDB(ctx)) + }) + t.Run("dump is too high", func(t *testing.T) { + binary.LittleEndian.PutUint32(start, 2) // start from the second block + require.NoError(t, os.WriteFile(inPath, append(start, b...), + os.ModePerm)) + *in = inPath + *incremental = true + + require.Error(t, restoreDB(ctx)) + }) + + *in = testDump + *incremental = false + }) + set.String("dump", saveDump, "") require.NoError(t, restoreDB(ctx)) } diff --git a/cli/server_test.go b/cli/server_test.go new file mode 100644 index 000000000..200c3e7d8 --- /dev/null +++ b/cli/server_test.go @@ -0,0 +1,124 @@ +package main + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/nspcc-dev/neo-go/cli/server" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" +) + +func TestServerStart(t *testing.T) { + tmpDir := t.TempDir() + goodCfg, err := config.LoadFile(filepath.Join("..", "config", "protocol.unit_testnet.yml")) + require.NoError(t, err, "could not load config") + ptr := &goodCfg + saveCfg := func(t *testing.T, f func(cfg *config.Config)) string { + cfg := *ptr + chainPath := filepath.Join(t.TempDir(), "neogotestchain") + cfg.ApplicationConfiguration.DBConfiguration.Type = "leveldb" + cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath = chainPath + f(&cfg) + out, err := yaml.Marshal(cfg) + require.NoError(t, err) + + cfgPath := filepath.Join(tmpDir, "protocol.unit_testnet.yml") + require.NoError(t, ioutil.WriteFile(cfgPath, out, os.ModePerm)) + t.Cleanup(func() { + require.NoError(t, os.Remove(cfgPath)) + }) + return cfgPath + } + + baseCmd := []string{"neo-go", "node", "--unittest", "--config-path", tmpDir} + e := newExecutor(t, false) + + t.Run("invalid config path", func(t *testing.T) { + e.RunWithError(t, baseCmd...) + }) + t.Run("bad logger config", func(t *testing.T) { + badConfigDir := t.TempDir() + logfile := filepath.Join(badConfigDir, "logdir") + require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + saveCfg(t, func(cfg *config.Config) { + cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log") + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("invalid storage", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ApplicationConfiguration.DBConfiguration.Type = "" + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("stateroot service is on && StateRootInHeader=true", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ApplicationConfiguration.StateRoot.Enabled = true + cfg.ProtocolConfiguration.StateRootInHeader = true + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("invalid Oracle config", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ApplicationConfiguration.Oracle.Enabled = true + cfg.ApplicationConfiguration.Oracle.UnlockWallet.Path = "bad_orc_wallet.json" + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("invalid consensus config", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ApplicationConfiguration.UnlockWallet.Path = "bad_consensus_wallet.json" + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("invalid Notary config", func(t *testing.T) { + t.Run("malformed config", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ProtocolConfiguration.P2PSigExtensions = false + cfg.ApplicationConfiguration.P2PNotary.Enabled = true + }) + e.RunWithError(t, baseCmd...) + }) + t.Run("invalid wallet", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) { + cfg.ProtocolConfiguration.P2PSigExtensions = true + cfg.ApplicationConfiguration.P2PNotary.Enabled = true + cfg.ApplicationConfiguration.P2PNotary.UnlockWallet.Path = "bad_notary_wallet.json" + }) + e.RunWithError(t, baseCmd...) + }) + }) + t.Run("good", func(t *testing.T) { + saveCfg(t, func(cfg *config.Config) {}) + + go func() { + e.Run(t, baseCmd...) + }() + + var line string + require.Eventually(t, func() bool { + line, err = e.Out.ReadString('\n') + if err != nil && err != io.EOF { + t.Fatalf(fmt.Sprintf("unexpected error while reading CLI output: %s", err)) + } + return err == nil + }, 2*time.Second, 100*time.Millisecond) + lines := strings.Split(server.Logo(), "\n") + for _, expected := range lines { + // It should be regexp, so escape all backslashes. + expected = strings.ReplaceAll(expected, `\`, `\\`) + e.checkLine(t, line, expected) + line = e.getNextLine(t) + } + e.checkNextLine(t, "") + e.checkEOF(t) + }) +} From b88f2b389f58cbb36f23b0b9551050e0cec0f15f Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 1 Feb 2022 12:46:30 +0300 Subject: [PATCH 2/7] cli: check execution state while saving invoked tx --- cli/smartcontract/smart_contract.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 607f56bc8..058e0eee5 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -653,18 +653,30 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, return sender, err } + out := ctx.String("out") resp, err = c.InvokeFunction(script, operation, params, cosigners) if err != nil { return sender, cli.NewExitError(err, 1) } - if signAndPush && resp.State != "HALT" { + if resp.State != "HALT" { errText := fmt.Sprintf("Warning: %s VM state returned from the RPC node: %s\n", resp.State, resp.FaultException) - if !ctx.Bool("force") { - return sender, cli.NewExitError(errText+". Use --force flag to send the transaction anyway.", 1) + action := "save" + process := "Saving" + if signAndPush { + if out != "" { + action += "and send" + process += "and sending" + } else { + action = "send" + process = "Sending" + } } - fmt.Fprintln(ctx.App.Writer, errText+". Sending transaction...") + if !ctx.Bool("force") { + return sender, cli.NewExitError(errText+". Use --force flag to "+action+" the transaction anyway.", 1) + } + fmt.Fprintln(ctx.App.Writer, errText+". "+process+" transaction...") } - if out := ctx.String("out"); out != "" { + if out != "" { tx, err := c.CreateTxFromScript(resp.Script, acc, resp.GasConsumed+int64(sysgas), int64(gas), cosignersAccounts) if err != nil { return sender, cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1) From 590f86aa2c7f3b23c2227c35d5047c996b9f598d Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 31 Jan 2022 16:24:15 +0300 Subject: [PATCH 3/7] cli: add tests for smartcontract-related commands Fixes: * Improve error handling. --- cli/contract_test.go | 172 +++++++++++++++++++++++++++++++++- cli/smartcontract/manifest.go | 4 +- cli/util_test.go | 25 +++++ 3 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 cli/util_test.go diff --git a/cli/contract_test.go b/cli/contract_test.go index c94dc1ba3..db7525b24 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -56,15 +56,24 @@ func TestCalcHash(t *testing.T) { t.Run("no manifest file", func(t *testing.T) { e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", nefPath)...) }) - t.Run("invalid path", func(t *testing.T) { + t.Run("invalid nef path", func(t *testing.T) { e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", "./testdata/verify.nef123", "--manifest", manifestPath)...) }) - t.Run("invalid file", func(t *testing.T) { + t.Run("invalid manifest path", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), + "--in", nefPath, "--manifest", "./testdata/verify.manifest123")...) + }) + t.Run("invalid nef file", func(t *testing.T) { p := filepath.Join(tmpDir, "neogo.calchash.verify.nef") require.NoError(t, ioutil.WriteFile(p, src[:4], os.ModePerm)) e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", p, "--manifest", manifestPath)...) }) + t.Run("invalid manifest file", func(t *testing.T) { + p := filepath.Join(tmpDir, "neogo.calchash.verify.manifest.json") + require.NoError(t, ioutil.WriteFile(p, manifestBytes[:4], os.ModePerm)) + e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", nefPath, "--manifest", p)...) + }) cmd = append(cmd, "--in", nefPath, "--manifest", manifestPath) expected := state.CreateContractHash(sender, nefF.Checksum, manif.Name) @@ -123,6 +132,13 @@ func TestContractInitAndCompile(t *testing.T) { cfgName := filepath.Join(ctrPath, "notexists.yml") e.RunWithError(t, append(cmd, "--config", cfgName)...) }) + t.Run("provided corrupted config", func(t *testing.T) { + data, err := ioutil.ReadFile(cfgPath) + require.NoError(t, err) + badCfg := filepath.Join(tmpDir, "bad.yml") + require.NoError(t, ioutil.WriteFile(badCfg, data[:len(data)-5], os.ModePerm)) + e.RunWithError(t, append(cmd, "--config", badCfg)...) + }) // Replace `pkg/interop` in go.mod to avoid getting an actual module version. goMod := filepath.Join(ctrPath, "go.mod") @@ -263,6 +279,45 @@ func TestDeployWithSigners(t *testing.T) { "--config", "testdata/deploy/neo-go.yml", "--out", nefName, "--manifest", manifestName) + t.Run("missing nef", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", validatorWallet, "--address", validatorAddr, + "--in", "", "--manifest", manifestName) + }) + t.Run("missing manifest", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", validatorWallet, "--address", validatorAddr, + "--in", nefName, "--manifest", "") + }) + t.Run("corrupted data", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", validatorWallet, "--address", validatorAddr, + "--in", nefName, "--manifest", manifestName, + "[", "str1") + }) + t.Run("invalid data", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", validatorWallet, "--address", validatorAddr, + "--in", nefName, "--manifest", manifestName, + "str1", "str2") + }) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "deploy", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--address", validatorAddr, + "--in", nefName, "--manifest", manifestName, + "[", "str1", "str2", "]") + }) + t.Run("missing RPC", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "deploy", + "--wallet", validatorWallet, "--address", validatorAddr, + "--in", nefName, "--manifest", manifestName, + "[", "str1", "str2", "]") + }) e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "deploy", "--rpc-endpoint", "http://"+e.RPC.Addr, @@ -292,6 +347,54 @@ func TestContractManifestGroups(t *testing.T) { "--config", "testdata/deploy/neo-go.yml", "--out", nefName, "--manifest", manifestName) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group") + }) + t.Run("invalid wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", t.TempDir()) + }) + t.Run("invalid account", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testWalletPath, "--account", "not-an-acc") + }) + t.Run("invalid sender", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testWalletPath, "--account", testWalletAccount, + "--sender", "not-a-sender") + }) + t.Run("invalid NEF file", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testWalletPath, "--account", testWalletAccount, + "--sender", testWalletAccount, "--nef", tmpDir) + }) + t.Run("corrupted NEF file", func(t *testing.T) { + f := filepath.Join(tmpDir, "invalid.nef") + require.NoError(t, os.WriteFile(f, []byte{1, 2, 3}, os.ModePerm)) + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testWalletPath, "--account", testWalletAccount, + "--sender", testWalletAccount, "--nef", f) + }) + t.Run("invalid manifest file", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testWalletPath, "--account", testWalletAccount, + "--sender", testWalletAccount, "--nef", nefName, + "--manifest", tmpDir) + }) + t.Run("corrupted manifest file", func(t *testing.T) { + f := filepath.Join(tmpDir, "invalid.manifest.json") + require.NoError(t, os.WriteFile(f, []byte{1, 2, 3}, os.ModePerm)) + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testWalletPath, "--account", testWalletAccount, + "--sender", testWalletAccount, "--nef", nefName, + "--manifest", f) + }) + t.Run("unknown account", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", + "--wallet", testWalletPath, "--account", util.Uint160{}.StringLE(), + "--sender", testWalletAccount, "--nef", nefName, + "--manifest", manifestName) + }) cmd := []string{"neo-go", "contract", "manifest", "add-group", "--nef", nefName, "--manifest", manifestName} @@ -338,6 +441,44 @@ func deployContract(t *testing.T, e *executor, inPath, configPath, wallet, addre return h } +func TestContract_TestInvokeScript(t *testing.T) { + e := newExecutor(t, true) + tmpDir := t.TempDir() + badNef := filepath.Join(tmpDir, "invalid.nef") + goodNef := filepath.Join(tmpDir, "deploy.nef") + manifestName := filepath.Join(tmpDir, "deploy.manifest.json") + e.Run(t, "neo-go", "contract", "compile", + "--in", "testdata/deploy/main.go", // compile single file + "--config", "testdata/deploy/neo-go.yml", + "--out", goodNef, "--manifest", manifestName) + + t.Run("missing in", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addr) + }) + t.Run("unexisting in", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--in", badNef) + }) + t.Run("invalid nef", func(t *testing.T) { + require.NoError(t, ioutil.WriteFile(badNef, []byte("qwer"), os.ModePerm)) + e.RunWithError(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--in", badNef) + }) + t.Run("invalid signers", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--in", goodNef, "--", "not-a-valid-signer") + }) + t.Run("no RPC endpoint", func(t *testing.T) { + e.RunWithError(t, "neo-go", "contract", "testinvokescript", + "--rpc-endpoint", "http://123456789", + "--in", goodNef) + }) +} + func TestComlileAndInvokeFunction(t *testing.T) { e := newExecutor(t, true) @@ -436,6 +577,33 @@ func TestComlileAndInvokeFunction(t *testing.T) { h.StringLE(), "getValue") e.RunWithError(t, cmd...) }) + t.Run("corrupted wallet", func(t *testing.T) { + tmp, err := ioutil.TempDir("", "tmp") + require.NoError(t, err) + tmpPath := filepath.Join(tmp, "wallet.json") + require.NoError(t, ioutil.WriteFile(tmpPath, []byte("{"), os.ModePerm)) + + cmd := append(cmd, "--wallet", tmpPath, + h.StringLE(), "getValue") + e.RunWithError(t, cmd...) + }) + t.Run("non-existent address", func(t *testing.T) { + cmd := append(cmd, "--wallet", validatorWallet, + "--address", random.Uint160().StringLE(), + h.StringLE(), "getValue") + e.RunWithError(t, cmd...) + }) + t.Run("invalid password", func(t *testing.T) { + e.In.WriteString("invalid_password\r") + cmd := append(cmd, "--wallet", validatorWallet, + h.StringLE(), "getValue") + e.RunWithError(t, cmd...) + }) + t.Run("good: default address", func(t *testing.T) { + e.In.WriteString("one\r") + e.In.WriteString("y\r") + e.Run(t, append(cmd, "--wallet", validatorWallet, h.StringLE(), "getValue")...) + }) cmd = append(cmd, "--wallet", validatorWallet, "--address", validatorAddr) t.Run("cancelled", func(t *testing.T) { diff --git a/cli/smartcontract/manifest.go b/cli/smartcontract/manifest.go index e7f5d4ee7..db315285a 100644 --- a/cli/smartcontract/manifest.go +++ b/cli/smartcontract/manifest.go @@ -29,12 +29,12 @@ func manifestAddGroup(ctx *cli.Context) error { addr, err := flags.ParseAddress(ctx.String("account")) if err != nil { - return cli.NewExitError(errors.New("address is missing"), 1) + return cli.NewExitError(fmt.Errorf("account is invalid or missing: %w", err), 1) } sender, err := flags.ParseAddress(ctx.String("sender")) if err != nil { - return cli.NewExitError("invalid sender", 1) + return cli.NewExitError(fmt.Errorf("invalid sender: %w", err), 1) } nf, _, err := readNEFFile(ctx.String("nef")) diff --git a/cli/util_test.go b/cli/util_test.go new file mode 100644 index 000000000..e845e7f37 --- /dev/null +++ b/cli/util_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/util" +) + +func TestUtilConvert(t *testing.T) { + e := newExecutor(t, false) + + e.Run(t, "neo-go", "util", "convert", util.Uint160{1, 2, 3}.StringLE()) + e.checkNextLine(t, "f975") // int to hex + e.checkNextLine(t, "\\+XU=") // int to base64 + e.checkNextLine(t, "NKuyBkoGdZZSLyPbJEetheRhMrGSCQx7YL") // BE to address + e.checkNextLine(t, "NL1JGiyJXdTkvFksXbFxgLJcWLj8Ewe7HW") // LE to address + e.checkNextLine(t, "Hex to String") // hex to string + e.checkNextLine(t, "5753853598078696051256155186041784866529345536") // hex to int + e.checkNextLine(t, "0102030000000000000000000000000000000000") // swap endianness + e.checkNextLine(t, "Base64 to String") // base64 to string + e.checkNextLine(t, "368753434210909009569191652203865891677393101439813372294890211308228051") // base64 to bigint + e.checkNextLine(t, "30303030303030303030303030303030303030303030303030303030303030303030303330323031") // string to hex + e.checkNextLine(t, "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAzMDIwMQ==") // string to base64 + e.checkEOF(t) +} From 152cebe4e2e7457807053110b3416a0aca5db45b Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 31 Jan 2022 16:26:23 +0300 Subject: [PATCH 4/7] cli: add tests for wallet-related commands Fixes: * Return proper exit code on error. * Improve error handling. --- cli/multisig_test.go | 60 +++++++++- cli/wallet/wallet.go | 11 +- cli/wallet_test.go | 262 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 309 insertions(+), 24 deletions(-) diff --git a/cli/multisig_test.go b/cli/multisig_test.go index f673b0af5..654f131ce 100644 --- a/cli/multisig_test.go +++ b/cli/multisig_test.go @@ -14,6 +14,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/stretchr/testify/require" ) @@ -64,6 +65,51 @@ func TestSignMultisigTx(t *testing.T) { priv, err := keys.NewPrivateKey() require.NoError(t, err) + t.Run("bad cases", func(t *testing.T) { + txPath := filepath.Join(tmpDir, "multisigtx.json") + t.Cleanup(func() { + os.Remove(txPath) + }) + e.In.WriteString("pass\r") + e.Run(t, "neo-go", "wallet", "nep17", "transfer", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", wallet1Path, "--from", multisigAddr, + "--to", priv.Address(), "--token", "NEO", "--amount", "1", + "--out", txPath) + + // missing wallet + e.RunWithError(t, "neo-go", "wallet", "sign") + + // missing in + e.RunWithError(t, "neo-go", "wallet", "sign", + "--wallet", wallet2Path) + + // missing address + e.RunWithError(t, "neo-go", "wallet", "sign", + "--wallet", wallet2Path, + "--in", txPath) + + // invalid address + e.RunWithError(t, "neo-go", "wallet", "sign", + "--wallet", wallet2Path, "--address", util.Uint160{}.StringLE(), + "--in", txPath) + + // invalid out + e.In.WriteString("pass\r") + e.RunWithError(t, "neo-go", "wallet", "sign", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", wallet2Path, "--address", multisigAddr, + "--in", txPath, "--out", t.TempDir()) + + // invalid RPC endpoint + e.In.WriteString("pass\r") + e.RunWithError(t, "neo-go", "wallet", "sign", + "--rpc-endpoint", "http://not-an-address", + "--wallet", wallet2Path, "--address", multisigAddr, + "--in", txPath) + }) + + // Create transaction and save it for further multisigning. txPath := filepath.Join(tmpDir, "multisigtx.json") t.Cleanup(func() { os.Remove(txPath) @@ -90,12 +136,6 @@ func TestSignMultisigTx(t *testing.T) { }) } - // missing address - e.RunWithError(t, "neo-go", "wallet", "sign", - "--rpc-endpoint", "http://"+e.RPC.Addr, - "--wallet", wallet2Path, - "--in", txPath, "--out", txPath) - t.Run("test invoke", func(t *testing.T) { t.Run("missing file", func(t *testing.T) { e.RunWithError(t, "neo-go", "util", "txdump") @@ -124,6 +164,14 @@ func TestSignMultisigTx(t *testing.T) { "--in", txPath, "--out", txPath) e.checkTxPersisted(t) + t.Run("double-sign", func(t *testing.T) { + e.In.WriteString("pass\r") + e.RunWithError(t, "neo-go", "wallet", "sign", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", wallet2Path, "--address", multisigAddr, + "--in", txPath, "--out", txPath) + }) + b, _ := e.Chain.GetGoverningTokenBalance(priv.GetScriptHash()) require.Equal(t, big.NewInt(1), b) b, _ = e.Chain.GetGoverningTokenBalance(multisigHash) diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index 0d62f1840..391326de1 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -356,7 +356,11 @@ func convertWallet(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - newWallet, err := wallet.NewWallet(ctx.String("out")) + out := ctx.String("out") + if len(out) == 0 { + return cli.NewExitError("missing out path", 1) + } + newWallet, err := wallet.NewWallet(out) if err != nil { return cli.NewExitError(err, 1) } @@ -375,7 +379,7 @@ func convertWallet(ctx *cli.Context) error { newWallet.AddAccount(newAcc) } if err := newWallet.Save(); err != nil { - return cli.NewExitError(err, -1) + return cli.NewExitError(err, 1) } return nil } @@ -605,7 +609,8 @@ func removeAccount(ctx *cli.Context) error { if err := wall.RemoveAccount(acc.Address); err != nil { return cli.NewExitError(fmt.Errorf("error on remove: %w", err), 1) - } else if err := wall.Save(); err != nil { + } + if err := wall.Save(); err != nil { return cli.NewExitError(fmt.Errorf("error while saving wallet: %w", err), 1) } return nil diff --git a/cli/wallet_test.go b/cli/wallet_test.go index dc36cb5f0..504ffe3a9 100644 --- a/cli/wallet_test.go +++ b/cli/wallet_test.go @@ -3,7 +3,6 @@ package main import ( "encoding/hex" "encoding/json" - "io/ioutil" "math/big" "path/filepath" "strings" @@ -14,6 +13,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" ) @@ -36,13 +36,34 @@ func TestWalletAccountRemove(t *testing.T) { w, err := wallet.NewWalletFromFile(walletPath) require.NoError(t, err) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "remove") + }) + t.Run("missing address", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "remove", "--wallet", walletPath) + }) + t.Run("invalid address", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "remove", "--wallet", walletPath, + "--address", util.Uint160{}.StringLE()) + }) + addr := w.Accounts[0].Address + t.Run("askForConsent > no", func(t *testing.T) { + e.In.WriteString("no") + e.Run(t, "neo-go", "wallet", "remove", "--wallet", walletPath, + "--address", addr) + actual, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + require.Equal(t, w, actual) + }) + e.Run(t, "neo-go", "wallet", "remove", "--wallet", walletPath, "--address", addr, "--force") - rawWallet, err := ioutil.ReadFile(walletPath) + actual, err := wallet.NewWalletFromFile(walletPath) require.NoError(t, err) - require.NoError(t, json.Unmarshal(rawWallet, new(wallet.Wallet))) + require.Equal(t, 1, len(actual.Accounts)) + require.Equal(t, w.Accounts[1], actual.Accounts[0]) } func TestWalletChangePassword(t *testing.T) { @@ -65,6 +86,12 @@ func TestWalletChangePassword(t *testing.T) { addr1 := w.Accounts[0].Address addr2 := w.Accounts[1].Address + t.Run("missing wallet path", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "change-password") + }) + t.Run("EOF reading old password", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "change-password", "--wallet", walletPath) + }) t.Run("bad old password", func(t *testing.T) { e.In.WriteString("ssap\r") e.In.WriteString("aaa\r") // Pretend for the password to be fine. @@ -112,9 +139,46 @@ func TestWalletChangePassword(t *testing.T) { } func TestWalletInit(t *testing.T) { - tmpDir := t.TempDir() e := newExecutor(t, false) + t.Run("missing path", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "init") + }) + t.Run("invalid path", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "init", "--wallet", t.TempDir()) + }) + t.Run("good: no account", func(t *testing.T) { + walletPath := filepath.Join(t.TempDir(), "wallet.json") + e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) + w, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + require.Equal(t, 0, len(w.Accounts)) + }) + t.Run("with account", func(t *testing.T) { + walletPath := filepath.Join(t.TempDir(), "wallet.json") + t.Run("missing acc name", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "init", "--wallet", walletPath, "--account") + }) + t.Run("missing pass", func(t *testing.T) { + e.In.WriteString("acc\r") + e.RunWithError(t, "neo-go", "wallet", "init", "--wallet", walletPath, "--account") + }) + t.Run("missing second pass", func(t *testing.T) { + e.In.WriteString("acc\r") + e.In.WriteString("pass\r") + e.RunWithError(t, "neo-go", "wallet", "init", "--wallet", walletPath, "--account") + }) + e.In.WriteString("acc\r") + e.In.WriteString("pass\r") + e.In.WriteString("pass\r") + e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath, "--account") + w, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + require.Equal(t, 1, len(w.Accounts)) + require.Equal(t, "acc", w.Accounts[0].Label) + }) + + tmpDir := t.TempDir() walletPath := filepath.Join(tmpDir, "wallet.json") e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) @@ -144,6 +208,12 @@ func TestWalletInit(t *testing.T) { t.Run("stdin", func(t *testing.T) { e.RunWithError(t, "neo-go", "wallet", "create", "--wallet", "-") }) + t.Run("passwords mismatch", func(t *testing.T) { + e.In.WriteString("testname\r") + e.In.WriteString("testpass\r") + e.In.WriteString("badpass\r") + e.RunWithError(t, "neo-go", "wallet", "create", "--wallet", walletPath) + }) e.In.WriteString("testname\r") e.In.WriteString("testpass\r") e.In.WriteString("testpass\r") @@ -171,6 +241,9 @@ func TestWalletInit(t *testing.T) { t.Run("Import", func(t *testing.T) { t.Run("WIF", func(t *testing.T) { + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import") + }) priv, err := keys.NewPrivateKey() require.NoError(t, err) e.In.WriteString("test_account\r") @@ -194,6 +267,33 @@ func TestWalletInit(t *testing.T) { e.RunWithError(t, "neo-go", "wallet", "import", "--wallet", walletPath, "--wif", priv.WIF()) }) + + t.Run("contract", func(t *testing.T) { + priv, err = keys.NewPrivateKey() + require.NoError(t, err) + + t.Run("invalid script", func(t *testing.T) { + e.In.WriteString("test_account_3\r") + e.In.WriteString("qwerty\r") + e.In.WriteString("qwerty\r") + e.RunWithError(t, "neo-go", "wallet", "import", + "--wallet", walletPath, "--wif", priv.WIF(), "--contract", "not-a-hex") + }) + + e.In.WriteString("test_account_3\r") + e.In.WriteString("qwerty\r") + e.In.WriteString("qwerty\r") + e.Run(t, "neo-go", "wallet", "import", + "--wallet", walletPath, "--wif", priv.WIF(), "--contract", "0a0b0c") + + w, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + t.Cleanup(w.Close) + acc := w.GetAccount(priv.GetScriptHash()) + require.NotNil(t, acc) + require.Equal(t, "test_account_3", acc.Label) + require.NoError(t, acc.Decrypt("qwerty", w.Scrypt)) + }) }) t.Run("EncryptedWIF", func(t *testing.T) { acc, err := wallet.NewAccount() @@ -218,12 +318,31 @@ func TestWalletInit(t *testing.T) { require.NoError(t, actual.Decrypt("somepass", w.Scrypt)) }) t.Run("Multisig", func(t *testing.T) { + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import-multisig") + }) + t.Run("insufficient pubs", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import-multisig", + "--wallet", walletPath, + "--min", "2") + }) privs, pubs := generateKeys(t, 4) - cmd := []string{"neo-go", "wallet", "import-multisig", "--wallet", walletPath, - "--wif", privs[0].WIF(), "--min", "2"} + t.Run("invalid pub encoding", func(t *testing.T) { + e.RunWithError(t, append(cmd, hex.EncodeToString(pubs[1].Bytes()), + hex.EncodeToString(pubs[1].Bytes()), + hex.EncodeToString(pubs[2].Bytes()), + "not-a-pub")...) + }) + t.Run("missing WIF", func(t *testing.T) { + e.RunWithError(t, append(cmd, hex.EncodeToString(pubs[0].Bytes()), + hex.EncodeToString(pubs[1].Bytes()), + hex.EncodeToString(pubs[2].Bytes()), + hex.EncodeToString(pubs[3].Bytes()))...) + }) + cmd = append(cmd, "--wif", privs[0].WIF()) t.Run("InvalidPublicKeys", func(t *testing.T) { e.In.WriteString("multiacc\r") e.In.WriteString("multipass\r") @@ -253,6 +372,16 @@ func TestWalletInit(t *testing.T) { require.NotNil(t, actual) require.NoError(t, actual.Decrypt("multipass", w.Scrypt)) require.Equal(t, script, actual.Contract.Script) + + t.Run("double-import", func(t *testing.T) { + e.In.WriteString("multiacc\r") + e.In.WriteString("multipass\r") + e.In.WriteString("multipass\r") + e.RunWithError(t, append(cmd, hex.EncodeToString(pubs[0].Bytes()), + hex.EncodeToString(pubs[1].Bytes()), + hex.EncodeToString(pubs[2].Bytes()), + hex.EncodeToString(pubs[3].Bytes()))...) + }) }) }) } @@ -260,6 +389,13 @@ func TestWalletInit(t *testing.T) { func TestWalletExport(t *testing.T) { e := newExecutor(t, false) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "export") + }) + t.Run("invalid address", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "export", + "--wallet", validatorWallet, "not-an-address") + }) t.Run("Encrypted", func(t *testing.T) { e.Run(t, "neo-go", "wallet", "export", "--wallet", validatorWallet, validatorAddr) @@ -274,6 +410,15 @@ func TestWalletExport(t *testing.T) { e.RunWithError(t, "neo-go", "wallet", "export", "--wallet", validatorWallet, "--decrypt") }) + t.Run("EOF reading password", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "export", + "--wallet", validatorWallet, "--decrypt", validatorAddr) + }) + t.Run("invalid password", func(t *testing.T) { + e.In.WriteString("invalid_pass\r") + e.RunWithError(t, "neo-go", "wallet", "export", + "--wallet", validatorWallet, "--decrypt", validatorAddr) + }) e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "export", "--wallet", validatorWallet, "--decrypt", validatorAddr) @@ -283,9 +428,39 @@ func TestWalletExport(t *testing.T) { }) } -func TestClaimGas(t *testing.T) { +func TestWalletClaimGas(t *testing.T) { e := newExecutor(t, true) + t.Run("missing wallet path", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "claim", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--address", testWalletAccount) + }) + t.Run("missing address", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "claim", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", testWalletPath) + }) + t.Run("invalid address", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "claim", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", testWalletPath, + "--address", util.Uint160{}.StringLE()) + }) + t.Run("missing endpoint", func(t *testing.T) { + e.In.WriteString("testpass\r") + e.RunWithError(t, "neo-go", "wallet", "claim", + "--wallet", testWalletPath, + "--address", testWalletAccount) + }) + t.Run("insufficient funds", func(t *testing.T) { + e.In.WriteString("testpass\r") + e.RunWithError(t, "neo-go", "wallet", "claim", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", testWalletPath, + "--address", testWalletAccount) + }) + args := []string{ "neo-go", "wallet", "nep17", "multitransfer", "--rpc-endpoint", "http://" + e.RPC.Addr, @@ -326,7 +501,7 @@ func TestClaimGas(t *testing.T) { } } -func TestImportDeployed(t *testing.T) { +func TestWalletImportDeployed(t *testing.T) { tmpDir := t.TempDir() e := newExecutor(t, true) h := deployVerifyContract(t, e) @@ -337,15 +512,43 @@ func TestImportDeployed(t *testing.T) { priv, err := keys.NewPrivateKey() require.NoError(t, err) - // missing contract sh - e.RunWithError(t, "neo-go", "wallet", "import-deployed", - "--rpc-endpoint", "http://"+e.RPC.Addr, - "--wallet", walletPath, "--wif", priv.WIF()) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import-deployed") + }) + t.Run("missing contract sh", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--wallet", walletPath) + }) + t.Run("missing WIF", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--wallet", walletPath, "--contract", h.StringLE()) + }) + t.Run("missing endpoint", func(t *testing.T) { + e.In.WriteString("acc\rpass\rpass\r") + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--wallet", walletPath, "--contract", h.StringLE(), + "--wif", priv.WIF()) + }) + t.Run("unknown contract", func(t *testing.T) { + e.In.WriteString("acc\rpass\rpass\r") + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", walletPath, "--contract", util.Uint160{}.StringLE(), + "--wif", priv.WIF()) + }) + t.Run("no `verify` method", func(t *testing.T) { + badH := deployNNSContract(t, e) // wrong contract with no `verify` method + e.In.WriteString("acc\rpass\rpass\r") + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", walletPath, "--contract", badH.StringLE(), + "--wif", priv.WIF()) + }) e.In.WriteString("acc\rpass\rpass\r") e.Run(t, "neo-go", "wallet", "import-deployed", "--rpc-endpoint", "http://"+e.RPC.Addr, - "--wallet", walletPath, "--wif", priv.WIF(), + "--wallet", walletPath, "--wif", priv.WIF(), "--name", "my_acc", "--contract", h.StringLE()) w, err := wallet.NewWalletFromFile(walletPath) @@ -358,6 +561,14 @@ func TestImportDeployed(t *testing.T) { require.Equal(t, address.Uint160ToString(h), contractAddr) require.True(t, w.Accounts[0].Contract.Deployed) + t.Run("re-importing", func(t *testing.T) { + e.In.WriteString("acc\rpass\rpass\r") + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", walletPath, "--wif", priv.WIF(), "--name", "my_acc", + "--contract", h.StringLE()) + }) + t.Run("Sign", func(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", @@ -389,6 +600,9 @@ func TestImportDeployed(t *testing.T) { func TestWalletDump(t *testing.T) { e := newExecutor(t, false) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "dump") + }) cmd := []string{"neo-go", "wallet", "dump", "--wallet", testWalletPath} e.Run(t, cmd...) rawStr := strings.TrimSpace(e.Out.String()) @@ -399,6 +613,9 @@ func TestWalletDump(t *testing.T) { t.Run("with decrypt", func(t *testing.T) { cmd = append(cmd, "--decrypt") + t.Run("EOF reading password", func(t *testing.T) { + e.RunWithError(t, cmd...) + }) t.Run("invalid password", func(t *testing.T) { e.In.WriteString("invalidpass\r") e.RunWithError(t, cmd...) @@ -414,8 +631,11 @@ func TestWalletDump(t *testing.T) { }) } -func TestDumpKeys(t *testing.T) { +func TestWalletDumpKeys(t *testing.T) { e := newExecutor(t, false) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "dump-keys") + }) cmd := []string{"neo-go", "wallet", "dump-keys", "--wallet", validatorWallet} pubRegex := "^0[23][a-hA-H0-9]{64}$" t.Run("all", func(t *testing.T) { @@ -432,6 +652,10 @@ func TestDumpKeys(t *testing.T) { e.checkNextLine(t, pubRegex) e.checkEOF(t) }) + t.Run("unknown address", func(t *testing.T) { + cmd := append(cmd, "--address", util.Uint160{}.StringLE()) + e.RunWithError(t, cmd...) + }) t.Run("simple signature", func(t *testing.T) { cmd := append(cmd, "--address", "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn") e.Run(t, cmd...) @@ -467,8 +691,16 @@ func TestWalletConvert(t *testing.T) { t.Run("missing wallet", func(t *testing.T) { e.RunWithError(t, cmd...) }) + cmd = append(cmd, "--wallet", "testdata/wallets/testwallet_NEO2.json") + t.Run("missing out path", func(t *testing.T) { + e.RunWithError(t, cmd...) + }) + t.Run("invalid out path", func(t *testing.T) { + dir := t.TempDir() + e.RunWithError(t, append(cmd, "--out", dir)...) + }) - cmd = append(cmd, "--wallet", "testdata/wallets/testwallet_NEO2.json", "--out", outPath) + cmd = append(cmd, "--out", outPath) t.Run("invalid password", func(t *testing.T) { // missing password e.RunWithError(t, cmd...) From 47465b8db95f8b17366e38f9fd4d0b8414db7c03 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 2 Feb 2022 13:01:29 +0300 Subject: [PATCH 5/7] codecov: add configuration file And allow the Codecov to post results even if some of our jobs or workflows are failing. --- codecov.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..dff9fb2bf --- /dev/null +++ b/codecov.yml @@ -0,0 +1,11 @@ +codecov: + require_ci_to_pass: no # We have several CI jobs that upload codecov results, + # and sometimes some of them may fail, it's OK. +coverage: + status: + project: + default: # This can be anything, but it needs to exist as the name + if_ci_failed: error #success, failure, error, ignore + only_pulls: false +comment: + after_n_builds: 1 From 4ea0aef9d5023da9f523067d94273ee2d7c28f22 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 2 Feb 2022 11:49:14 +0300 Subject: [PATCH 6/7] github: update coverage job See https://github.com/codecov/codecov-action#%EF%B8%8F--deprecration-of-v1 --- .github/workflows/run_tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index b7e6c81a3..5c4f3c83c 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -59,10 +59,10 @@ jobs: run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic -coverpkg=./pkg...,./cli/... - name: Upload coverage results to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: - fail_ci_if_error: false - path_to_write_report: ./coverage.txt + fail_ci_if_error: true # if something is wrong on uploading codecov results, then this job will fail + files: ./coverage.txt verbose: true tests: From ba49209d8aee25913b79996056a0099065050c5d Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 2 Feb 2022 12:54:28 +0300 Subject: [PATCH 7/7] cli: use ioutil instead of os to read/write files Go 1.15 doesn't have os.ReadFile and os.WiteFile. --- cli/contract_test.go | 4 ++-- cli/dump_test.go | 4 ++-- cli/server/server_test.go | 16 ++++++++-------- cli/server_test.go | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cli/contract_test.go b/cli/contract_test.go index db7525b24..7ecacd70d 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -370,7 +370,7 @@ func TestContractManifestGroups(t *testing.T) { }) t.Run("corrupted NEF file", func(t *testing.T) { f := filepath.Join(tmpDir, "invalid.nef") - require.NoError(t, os.WriteFile(f, []byte{1, 2, 3}, os.ModePerm)) + require.NoError(t, ioutil.WriteFile(f, []byte{1, 2, 3}, os.ModePerm)) e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", "--wallet", testWalletPath, "--account", testWalletAccount, "--sender", testWalletAccount, "--nef", f) @@ -383,7 +383,7 @@ func TestContractManifestGroups(t *testing.T) { }) t.Run("corrupted manifest file", func(t *testing.T) { f := filepath.Join(tmpDir, "invalid.manifest.json") - require.NoError(t, os.WriteFile(f, []byte{1, 2, 3}, os.ModePerm)) + require.NoError(t, ioutil.WriteFile(f, []byte{1, 2, 3}, os.ModePerm)) e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", "--wallet", testWalletPath, "--account", testWalletAccount, "--sender", testWalletAccount, "--nef", nefName, diff --git a/cli/dump_test.go b/cli/dump_test.go index 63cc84936..1d42283eb 100644 --- a/cli/dump_test.go +++ b/cli/dump_test.go @@ -61,7 +61,7 @@ func TestDBRestoreDump(t *testing.T) { t.Run("bad logger config", func(t *testing.T) { badConfigDir := t.TempDir() logfile := filepath.Join(badConfigDir, "logdir") - require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + require.NoError(t, ioutil.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) cfg = loadConfig(t) cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log") out, err = yaml.Marshal(cfg) @@ -76,7 +76,7 @@ func TestDBRestoreDump(t *testing.T) { t.Run("bad storage config", func(t *testing.T) { badConfigDir := t.TempDir() logfile := filepath.Join(badConfigDir, "logdir") - require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + require.NoError(t, ioutil.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) cfg = loadConfig(t) cfg.ApplicationConfiguration.DBConfiguration.Type = "" out, err = yaml.Marshal(cfg) diff --git a/cli/server/server_test.go b/cli/server/server_test.go index 8046a457f..7d0e8ebce 100644 --- a/cli/server/server_test.go +++ b/cli/server/server_test.go @@ -47,7 +47,7 @@ func TestHandleLoggingParams(t *testing.T) { t.Run("logdir is a file", func(t *testing.T) { logfile := filepath.Join(d, "logdir") - require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + require.NoError(t, ioutil.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) set := flag.NewFlagSet("flagSet", flag.ExitOnError) ctx := cli.NewContext(cli.NewApp(), set, nil) cfg := config.ApplicationConfiguration{ @@ -181,7 +181,7 @@ func TestRestoreDB(t *testing.T) { t.Run("invalid logger path", func(t *testing.T) { badCfgDir := t.TempDir() logfile := filepath.Join(badCfgDir, "logdir") - require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + require.NoError(t, ioutil.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) cfg, err := config.LoadFile(filepath.Join(goodCfg, "protocol.privnet.yml")) require.NoError(t, err, "could not load config") cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log") @@ -223,7 +223,7 @@ func TestRestoreDB(t *testing.T) { }) t.Run("corrupted in: invalid block count", func(t *testing.T) { inPath := filepath.Join(t.TempDir(), "file3.acc") - require.NoError(t, os.WriteFile(inPath, []byte{1, 2, 3}, // file is expected to start from uint32 + require.NoError(t, ioutil.WriteFile(inPath, []byte{1, 2, 3}, // file is expected to start from uint32 os.ModePerm)) *in = inPath require.Error(t, restoreDB(ctx)) @@ -232,10 +232,10 @@ func TestRestoreDB(t *testing.T) { }) t.Run("corrupted in: corrupted block", func(t *testing.T) { inPath := filepath.Join(t.TempDir(), "file3.acc") - b, err := os.ReadFile(testDump) + b, err := ioutil.ReadFile(testDump) require.NoError(t, err) b[5] = 0xff // file is expected to start from uint32 (4 bytes) followed by the first block, so corrupt the first block bytes - require.NoError(t, os.WriteFile(inPath, b, os.ModePerm)) + require.NoError(t, ioutil.WriteFile(inPath, b, os.ModePerm)) *in = inPath require.Error(t, restoreDB(ctx)) @@ -243,12 +243,12 @@ func TestRestoreDB(t *testing.T) { }) t.Run("incremental dump", func(t *testing.T) { inPath := filepath.Join(t.TempDir(), "file1_incremental.acc") - b, err := os.ReadFile(testDump) + b, err := ioutil.ReadFile(testDump) require.NoError(t, err) start := make([]byte, 4) t.Run("good", func(t *testing.T) { binary.LittleEndian.PutUint32(start, 1) // start from the first block - require.NoError(t, os.WriteFile(inPath, append(start, b...), + require.NoError(t, ioutil.WriteFile(inPath, append(start, b...), os.ModePerm)) *in = inPath *incremental = true @@ -257,7 +257,7 @@ func TestRestoreDB(t *testing.T) { }) t.Run("dump is too high", func(t *testing.T) { binary.LittleEndian.PutUint32(start, 2) // start from the second block - require.NoError(t, os.WriteFile(inPath, append(start, b...), + require.NoError(t, ioutil.WriteFile(inPath, append(start, b...), os.ModePerm)) *in = inPath *incremental = true diff --git a/cli/server_test.go b/cli/server_test.go index 200c3e7d8..308ce98fc 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -47,7 +47,7 @@ func TestServerStart(t *testing.T) { t.Run("bad logger config", func(t *testing.T) { badConfigDir := t.TempDir() logfile := filepath.Join(badConfigDir, "logdir") - require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) + require.NoError(t, ioutil.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm)) saveCfg(t, func(cfg *config.Config) { cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log") })