cli: add tests for NEP5 balance querying
This commit is contained in:
parent
282b55494b
commit
468f7ee650
6 changed files with 340 additions and 4 deletions
148
cli/executor_test.go
Normal file
148
cli/executor_test.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/input"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/network"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpc/server"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zaptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
validatorAddr = "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK"
|
||||||
|
|
||||||
|
validatorWallet = "testdata/wallet1_solo.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var validatorHash, _ = address.StringToUint160(validatorAddr)
|
||||||
|
|
||||||
|
// executor represents context for a test instance.
|
||||||
|
// It can be safely used in multiple tests, but not in parallel.
|
||||||
|
type executor struct {
|
||||||
|
// CLI is a cli application to test.
|
||||||
|
CLI *cli.App
|
||||||
|
// Chain is a blockchain instance (can be empty).
|
||||||
|
Chain *core.Blockchain
|
||||||
|
// RPC is an RPC server to query (can be empty).
|
||||||
|
RPC *server.Server
|
||||||
|
// NetSrv is a network server (can be empty).
|
||||||
|
NetSrv *network.Server
|
||||||
|
// Out contains command output.
|
||||||
|
Out *bytes.Buffer
|
||||||
|
// Err contains command errors.
|
||||||
|
Err *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestChain(t *testing.T) (*core.Blockchain, *server.Server, *network.Server) {
|
||||||
|
configPath := "../config/protocol.unit_testnet.single.yml"
|
||||||
|
cfg, err := config.LoadFile(configPath)
|
||||||
|
require.NoError(t, err, "could not load config")
|
||||||
|
|
||||||
|
memoryStore := storage.NewMemoryStore()
|
||||||
|
logger := zaptest.NewLogger(t)
|
||||||
|
chain, err := core.NewBlockchain(memoryStore, cfg.ProtocolConfiguration, logger)
|
||||||
|
require.NoError(t, err, "could not create chain")
|
||||||
|
|
||||||
|
go chain.Run()
|
||||||
|
|
||||||
|
serverConfig := network.NewServerConfig(cfg)
|
||||||
|
netSrv, err := network.NewServer(serverConfig, chain, zap.NewNop())
|
||||||
|
require.NoError(t, err)
|
||||||
|
go netSrv.Start(make(chan error, 1))
|
||||||
|
rpcServer := server.New(chain, cfg.ApplicationConfiguration.RPC, netSrv, logger)
|
||||||
|
errCh := make(chan error, 2)
|
||||||
|
rpcServer.Start(errCh)
|
||||||
|
|
||||||
|
return chain, &rpcServer, netSrv
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExecutor(t *testing.T, needChain bool) *executor {
|
||||||
|
e := &executor{
|
||||||
|
CLI: newApp(),
|
||||||
|
Out: bytes.NewBuffer(nil),
|
||||||
|
Err: bytes.NewBuffer(nil),
|
||||||
|
}
|
||||||
|
e.CLI.Writer = e.Out
|
||||||
|
e.CLI.ErrWriter = e.Err
|
||||||
|
if needChain {
|
||||||
|
e.Chain, e.RPC, e.NetSrv = newTestChain(t)
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *executor) Close(t *testing.T) {
|
||||||
|
input.Terminal = nil
|
||||||
|
if e.RPC != nil {
|
||||||
|
require.NoError(t, e.RPC.Shutdown())
|
||||||
|
}
|
||||||
|
if e.NetSrv != nil {
|
||||||
|
e.NetSrv.Shutdown()
|
||||||
|
}
|
||||||
|
if e.Chain != nil {
|
||||||
|
e.Chain.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *executor) checkNextLine(t *testing.T, expected string) {
|
||||||
|
line, err := e.Out.ReadString('\n')
|
||||||
|
require.NoError(t, err)
|
||||||
|
e.checkLine(t, line, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *executor) checkLine(t *testing.T, line, expected string) {
|
||||||
|
require.Regexp(t, expected, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *executor) checkEOF(t *testing.T) {
|
||||||
|
_, err := e.Out.ReadString('\n')
|
||||||
|
require.True(t, errors.Is(err, io.EOF))
|
||||||
|
}
|
||||||
|
|
||||||
|
func setExitFunc() <-chan int {
|
||||||
|
ch := make(chan int, 1)
|
||||||
|
cli.OsExiter = func(code int) {
|
||||||
|
ch <- code
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkExit(t *testing.T, ch <-chan int, code int) {
|
||||||
|
select {
|
||||||
|
case c := <-ch:
|
||||||
|
require.Equal(t, code, c)
|
||||||
|
default:
|
||||||
|
if code != 0 {
|
||||||
|
require.Fail(t, "no exit was called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithError runs command and checks that is exits with error.
|
||||||
|
func (e *executor) RunWithError(t *testing.T, args ...string) {
|
||||||
|
ch := setExitFunc()
|
||||||
|
require.Error(t, e.run(args...))
|
||||||
|
checkExit(t, ch, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs command and checks that there were no errors.
|
||||||
|
func (e *executor) Run(t *testing.T, args ...string) {
|
||||||
|
ch := setExitFunc()
|
||||||
|
require.NoError(t, e.run(args...))
|
||||||
|
checkExit(t, ch, 0)
|
||||||
|
}
|
||||||
|
func (e *executor) run(args ...string) error {
|
||||||
|
e.Out.Reset()
|
||||||
|
e.Err.Reset()
|
||||||
|
return e.CLI.Run(args)
|
||||||
|
}
|
13
cli/main.go
13
cli/main.go
|
@ -13,6 +13,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
ctl := newApp()
|
||||||
|
|
||||||
|
if err := ctl.Run(os.Args); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newApp() *cli.App {
|
||||||
ctl := cli.NewApp()
|
ctl := cli.NewApp()
|
||||||
ctl.Name = "neo-go"
|
ctl.Name = "neo-go"
|
||||||
ctl.Version = config.Version
|
ctl.Version = config.Version
|
||||||
|
@ -23,8 +31,5 @@ func main() {
|
||||||
ctl.Commands = append(ctl.Commands, wallet.NewCommands()...)
|
ctl.Commands = append(ctl.Commands, wallet.NewCommands()...)
|
||||||
ctl.Commands = append(ctl.Commands, vm.NewCommands()...)
|
ctl.Commands = append(ctl.Commands, vm.NewCommands()...)
|
||||||
ctl.Commands = append(ctl.Commands, util.NewCommands()...)
|
ctl.Commands = append(ctl.Commands, util.NewCommands()...)
|
||||||
|
return ctl
|
||||||
if err := ctl.Run(os.Args); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
13
cli/main_test.go
Normal file
13
cli/main_test.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCLIVersion(t *testing.T) {
|
||||||
|
e := newExecutor(t, false)
|
||||||
|
defer e.Close(t)
|
||||||
|
e.Run(t, "neo-go", "--version")
|
||||||
|
e.checkNextLine(t, "^neo-go version")
|
||||||
|
e.checkEOF(t)
|
||||||
|
}
|
46
cli/nep5_test.go
Normal file
46
cli/nep5_test.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNEP5Balance(t *testing.T) {
|
||||||
|
e := newExecutor(t, true)
|
||||||
|
defer e.Close(t)
|
||||||
|
cmd := []string{
|
||||||
|
"neo-go", "wallet", "nep5", "balance",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addr,
|
||||||
|
"--wallet", validatorWallet,
|
||||||
|
"--addr", validatorAddr,
|
||||||
|
}
|
||||||
|
t.Run("NEO", func(t *testing.T) {
|
||||||
|
b, index := e.Chain.GetGoverningTokenBalance(validatorHash)
|
||||||
|
checkResult := func(t *testing.T) {
|
||||||
|
e.checkNextLine(t, "^\\s*TokenHash:\\s*"+e.Chain.GoverningTokenHash().StringLE())
|
||||||
|
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String())
|
||||||
|
e.checkNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
|
||||||
|
e.checkEOF(t)
|
||||||
|
}
|
||||||
|
t.Run("Alias", func(t *testing.T) {
|
||||||
|
e.Run(t, append(cmd, "--token", "neo")...)
|
||||||
|
checkResult(t)
|
||||||
|
})
|
||||||
|
t.Run("Hash", func(t *testing.T) {
|
||||||
|
e.Run(t, append(cmd, "--token", e.Chain.GoverningTokenHash().StringLE())...)
|
||||||
|
checkResult(t)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("GAS", func(t *testing.T) {
|
||||||
|
e.Run(t, append(cmd, "--token", "gas")...)
|
||||||
|
e.checkNextLine(t, "^\\s*TokenHash:\\s*"+e.Chain.UtilityTokenHash().StringLE())
|
||||||
|
b := e.Chain.GetUtilityTokenBalance(validatorHash)
|
||||||
|
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+util.Fixed8(b.Int64()).String())
|
||||||
|
})
|
||||||
|
t.Run("Invalid", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(cmd, "--token", "kek")...)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
72
cli/testdata/wallet1_solo.json
vendored
Normal file
72
cli/testdata/wallet1_solo.json
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"version": "3.0",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"address": "NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc",
|
||||||
|
"key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcILQZVEDXg=",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isdefault": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY",
|
||||||
|
"key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter1",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter2",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isdefault": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK",
|
||||||
|
"key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEQtBE43vrw==",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isdefault": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scrypt": {
|
||||||
|
"n": 16384,
|
||||||
|
"r": 8,
|
||||||
|
"p": 8
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"Tokens": null
|
||||||
|
}
|
||||||
|
}
|
52
config/protocol.unit_testnet.single.yml
Normal file
52
config/protocol.unit_testnet.single.yml
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
ProtocolConfiguration:
|
||||||
|
Magic: 42
|
||||||
|
SecondsPerBlock: 1
|
||||||
|
MemPoolSize: 100
|
||||||
|
StandbyCommittee:
|
||||||
|
- 02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2
|
||||||
|
ValidatorsCount: 1
|
||||||
|
VerifyBlocks: true
|
||||||
|
VerifyTransactions: true
|
||||||
|
|
||||||
|
ApplicationConfiguration:
|
||||||
|
# LogPath could be set up in case you need stdout logs to some proper file.
|
||||||
|
# LogPath: "./log/neogo.log"
|
||||||
|
DBConfiguration:
|
||||||
|
Type: "inmemory" #other options: 'inmemory','redis','boltdb', 'badgerdb'.
|
||||||
|
# DB type options. Uncomment those you need in case you want to switch DB type.
|
||||||
|
# LevelDBOptions:
|
||||||
|
# DataDirectoryPath: "./chains/unit_testnet"
|
||||||
|
# RedisDBOptions:
|
||||||
|
# Addr: "localhost:6379"
|
||||||
|
# Password: ""
|
||||||
|
# DB: 0
|
||||||
|
# BoltDBOptions:
|
||||||
|
# FilePath: "./chains/unit_testnet.bolt"
|
||||||
|
# BadgerDBOptions:
|
||||||
|
# BadgerDir: "./chains/unit_testnet.badger"
|
||||||
|
# Uncomment in order to set up custom address for node.
|
||||||
|
# Address: 127.0.0.1
|
||||||
|
NodePort: 0
|
||||||
|
Relay: true
|
||||||
|
DialTimeout: 3
|
||||||
|
ProtoTickInterval: 2
|
||||||
|
PingInterval: 30
|
||||||
|
PingTimeout: 90
|
||||||
|
MinPeers: 0
|
||||||
|
MaxPeers: 10
|
||||||
|
AttemptConnPeers: 5
|
||||||
|
UnlockWallet:
|
||||||
|
Path: "testdata/wallet1_solo.json"
|
||||||
|
Password: "one"
|
||||||
|
RPC:
|
||||||
|
Address: 127.0.0.1
|
||||||
|
MaxGasInvoke: 10
|
||||||
|
Enabled: true
|
||||||
|
EnableCORSWorkaround: false
|
||||||
|
Port: 0 # let the system choose port dynamically
|
||||||
|
Prometheus:
|
||||||
|
Enabled: false #since it's not useful for unit tests.
|
||||||
|
Port: 2112
|
||||||
|
Pprof:
|
||||||
|
Enabled: false #since it's not useful for unit tests.
|
||||||
|
Port: 2113
|
Loading…
Reference in a new issue