Daily engine improvements:

New GetExchangeOTPs API
CLI validation
Standardised pairs for GCTCLI
Expand test coverage
Trim SMS global from name is len > 11
Linter fixes
This commit is contained in:
Adrian Gallagher
2019-06-11 17:02:00 +10:00
parent f777e68716
commit 2ad808e70c
12 changed files with 702 additions and 415 deletions

View File

@@ -218,6 +218,31 @@ func getExchangeOTPCode(c *cli.Context) error {
return nil
}
var getExchangeOTPsCommand = cli.Command{
Name: "getexchangeotps",
Usage: "gets all exchange OTPs",
Action: getExchangeOTPCodes,
}
func getExchangeOTPCodes(c *cli.Context) error {
conn, err := setupClient()
if err != nil {
return err
}
defer conn.Close()
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetExchangeOTPCodes(context.Background(),
&gctrpc.GetExchangeOTPsRequest{})
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var getExchangeInfoCommand = cli.Command{
Name: "getexchangeinfo",
Usage: "gets a specific exchanges info",
@@ -320,7 +345,11 @@ func getTicker(c *cli.Context) error {
assetType = c.Args().Get(2)
}
p := currency.NewPairFromString(currencyPair)
if !validPair(currencyPair) {
return errInvalidPair
}
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetTicker(context.Background(),
&gctrpc.GetTickerRequest{
@@ -420,7 +449,11 @@ func getOrderbook(c *cli.Context) error {
assetType = c.Args().Get(2)
}
p := currency.NewPairFromString(currencyPair)
if !validPair(currencyPair) {
return errInvalidPair
}
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetOrderbook(context.Background(),
&gctrpc.GetOrderbookRequest{
@@ -832,7 +865,11 @@ func getOrders(c *cli.Context) error {
currencyPair = c.Args().Get(2)
}
p := currency.NewPairFromString(currencyPair)
if !validPair(currencyPair) {
return errInvalidPair
}
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.GetOrders(context.Background(), &gctrpc.GetOrdersRequest{
Exchange: exchangeName,
@@ -1007,7 +1044,11 @@ func submitOrder(c *cli.Context) error {
clientID = c.Args().Get(6)
}
p := currency.NewPairFromString(currencyPair)
if !validPair(currencyPair) {
return errInvalidPair
}
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.SubmitOrder(context.Background(), &gctrpc.SubmitOrderRequest{
Exchange: exchangeName,
@@ -1121,8 +1162,12 @@ func cancelOrder(c *cli.Context) error {
var p currency.Pair
if len(currencyPair) > 0 {
p = currency.NewPairFromString(currencyPair)
if !validPair(currencyPair) {
return errInvalidPair
}
p = currency.NewPairDelimiter(currencyPair, pairDelimiter)
}
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.CancelOrder(context.Background(), &gctrpc.CancelOrderRequest{
Exchange: exchangeName,
@@ -1329,7 +1374,11 @@ func addEvent(c *cli.Context) error {
}
defer conn.Close()
p := currency.NewPairFromString(currencyPair)
if !validPair(currencyPair) {
return errInvalidPair
}
p := currency.NewPairDelimiter(currencyPair, pairDelimiter)
client := gctrpc.NewGoCryptoTraderClient(conn)
result, err := client.AddEvent(context.Background(), &gctrpc.AddEventRequest{
Exchange: exchangeName,

View File

@@ -17,9 +17,10 @@ import (
)
var (
host string
username string
password string
host string
username string
password string
pairDelimiter string
)
func jsonOutput(in interface{}) {
@@ -75,6 +76,12 @@ func main() {
Usage: "the gRPC password",
Destination: &password,
},
cli.StringFlag{
Name: "delimiter",
Value: "-",
Usage: "the default pair delimiter used to standardise currency pair input",
Destination: &pairDelimiter,
},
}
app.Commands = []cli.Command{
getInfoCommand,
@@ -82,6 +89,7 @@ func main() {
enableExchangeCommand,
disableExchangeCommand,
getExchangeOTPCommand,
getExchangeOTPsCommand,
getExchangeInfoCommand,
getTickerCommand,
getTickersCommand,

14
cmd/gctcli/validation.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"errors"
"strings"
)
var (
errInvalidPair = errors.New("invalid currency pair supplied")
)
func validPair(pair string) bool {
return strings.Contains(pair, pairDelimiter)
}

View File

@@ -333,6 +333,11 @@ func (c *Config) CheckCommunicationsConfig() {
c.Communications.SMSGlobalConfig.From = c.Name
}
if len(c.Communications.SMSGlobalConfig.From) > 11 {
log.Warnf("SMSGlobal config supplied from name exceeds 11 characters, trimming.")
c.Communications.SMSGlobalConfig.From = c.Communications.SMSGlobalConfig.From[:11]
}
if c.SMS != nil {
// flush old SMS config
c.SMS = nil

View File

@@ -29,9 +29,33 @@ import (
"github.com/thrasher-/gocryptotrader/utils"
)
// GetOTPByExchange returns a OTP code for the desired exchange
// GetExchangeOTPs returns OTP codes for all exchanges which have a otpsecret
// stored
func GetExchangeOTPs() (map[string]string, error) {
otpCodes := make(map[string]string)
for x := range Bot.Config.Exchanges {
if otpSecret := Bot.Config.Exchanges[x].API.Credentials.OTPSecret; otpSecret != "" {
exchName := Bot.Config.Exchanges[x].Name
o, err := totp.GenerateCode(otpSecret, time.Now())
if err != nil {
log.Errorf("Unable to generate OTP code for exchange %s. Err: %s",
exchName, err)
continue
}
otpCodes[exchName] = o
}
}
if len(otpCodes) == 0 {
return nil, errors.New("no exchanges found which have a OTP secret stored")
}
return otpCodes, nil
}
// GetExchangeoOTPByName returns a OTP code for the desired exchange
// if it exists
func GetOTPByExchange(exchName string) (string, error) {
func GetExchangeoOTPByName(exchName string) (string, error) {
for x := range Bot.Config.Exchanges {
if !strings.EqualFold(Bot.Config.Exchanges[x].Name, exchName) {
continue
@@ -41,7 +65,7 @@ func GetOTPByExchange(exchName string) (string, error) {
return totp.GenerateCode(otpSecret, time.Now())
}
}
return "", errors.New("exchange does not have a otpsecret stored")
return "", errors.New("exchange does not have a OTP secret stored")
}
// GetAuthAPISupportedExchanges returns a list of auth api enabled exchanges
@@ -60,6 +84,7 @@ func GetAuthAPISupportedExchanges() []string {
func IsOnline() bool {
if Bot.Connectivity == nil {
log.Warnf("IsOnline called but Bot.Connectivity is nil")
return false
}
return Bot.Connectivity.IsConnected()
}

View File

@@ -2,9 +2,11 @@ package engine
import (
"testing"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/connchecker"
"github.com/thrasher-/gocryptotrader/currency"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/assets"
@@ -42,6 +44,110 @@ func SetupTestHelpers(t *testing.T) {
}
}
func TestGetExchangeOTPs(t *testing.T) {
SetupTestHelpers(t)
_, err := GetExchangeOTPs()
if err == nil {
t.Fatal("Expected err with no exchange OTP secrets set")
}
bfxCfg, err := Bot.Config.GetExchangeConfig("Bitfinex")
if err != nil {
t.Fatal(err)
}
bCfg, err := Bot.Config.GetExchangeConfig("Bitstamp")
if err != nil {
t.Fatal(err)
}
bfxCfg.API.Credentials.OTPSecret = "JBSWY3DPEHPK3PXP"
bCfg.API.Credentials.OTPSecret = "JBSWY3DPEHPK3PXP"
result, err := GetExchangeOTPs()
if err != nil {
t.Fatal(err)
}
if len(result) != 2 {
t.Fatal("Expected 2 OTP results")
}
bfxCfg.API.Credentials.OTPSecret = "°"
result, err = GetExchangeOTPs()
if err != nil {
t.Fatal(err)
}
if len(result) != 1 {
t.Fatal("Expected 1 OTP code with invalid OTP Secret")
}
// Flush settings
bfxCfg.API.Credentials.OTPSecret = ""
bCfg.API.Credentials.OTPSecret = ""
}
func TestGetExchangeoOTPByName(t *testing.T) {
SetupTestHelpers(t)
_, err := GetExchangeoOTPByName("Bitstamp")
if err == nil {
t.Fatal("Expected err with no exchange OTP secrets set")
}
bCfg, err := Bot.Config.GetExchangeConfig("Bitstamp")
if err != nil {
t.Fatal(err)
}
bCfg.API.Credentials.OTPSecret = "JBSWY3DPEHPK3PXP"
result, err := GetExchangeoOTPByName("Bitstamp")
if err != nil {
t.Fatal(err)
}
if result == "" {
t.Fatal("Expected valid OTP code")
}
}
func TestGetAuthAPISupportedExchanges(t *testing.T) {
SetupTestHelpers(t)
if result := GetAuthAPISupportedExchanges(); result != nil {
t.Fatal("Unexpected result")
}
}
func TestIsOnline(t *testing.T) {
SetupTestHelpers(t)
if r := IsOnline(); r {
t.Fatal("Unexpected result")
}
var err error
Bot.Connectivity, err = connchecker.New(Bot.Config.ConnectionMonitor.DNSList,
Bot.Config.ConnectionMonitor.PublicDomainList,
Bot.Config.ConnectionMonitor.CheckInterval)
if err != nil {
t.Fatal(err)
}
tick := time.NewTicker(time.Second * 5)
for {
select {
case <-tick.C:
t.Fatal("Test timeout")
default:
if IsOnline() {
Bot.Connectivity.Shutdown()
return
}
}
}
}
func TestGetAvailableExchanges(t *testing.T) {
SetupTestHelpers(t)
if r := len(GetAvailableExchanges()); r == 0 {
t.Error("Expected len > 0")
}
}
func TestGetSpecificAvailablePairs(t *testing.T) {
SetupTestHelpers(t)
assetType := assets.AssetTypeSpot
@@ -255,7 +361,6 @@ func TestGetExchangeNamesByCurrency(t *testing.T) {
}
result = GetExchangeNamesByCurrency(currency.NewPairFromStrings("BTC", "JPY"), true, assetType)
t.Log(result)
if !common.StringDataCompare(result, "Bitflyer") {
t.Fatal("Unexpected result")
}

View File

@@ -178,10 +178,17 @@ func (s *RPCServer) EnableExchange(ctx context.Context, r *gctrpc.GenericExchang
// GetExchangeOTPCode retrieves an exchanges OTP code
func (s *RPCServer) GetExchangeOTPCode(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GetExchangeOTPReponse, error) {
result, err := GetOTPByExchange(r.Exchange)
result, err := GetExchangeoOTPByName(r.Exchange)
return &gctrpc.GetExchangeOTPReponse{OtpCode: result}, err
}
// GetExchangeOTPCodes retrieves OTP codes for all exchanges which have an
// OTP secret installed
func (s *RPCServer) GetExchangeOTPCodes(ctx context.Context, r *gctrpc.GetExchangeOTPsRequest) (*gctrpc.GetExchangeOTPsResponse, error) {
result, err := GetExchangeOTPs()
return &gctrpc.GetExchangeOTPsResponse{OtpCodes: result}, err
}
// GetExchangeInfo gets info for a specific exchange
func (s *RPCServer) GetExchangeInfo(ctx context.Context, r *gctrpc.GenericExchangeNameRequest) (*gctrpc.GetExchangeInfoResponse, error) {
exchCfg, err := Bot.Config.GetExchangeConfig(r.Exchange)

View File

@@ -419,8 +419,8 @@ func TestSubmitOrder(t *testing.T) {
var orderSubmission = &exchange.OrderSubmission{
Pair: currency.Pair{
Base: currency.BTC,
Quote: currency.USD,
Base: currency.BTC,
Quote: currency.USD,
},
OrderSide: exchange.BuyOrderSide,
OrderType: exchange.LimitOrderType,

File diff suppressed because it is too large Load Diff

View File

@@ -9,13 +9,13 @@ It translates gRPC into RESTful JSON APIs.
package gctrpc
import (
"context"
"io"
"net/http"
"github.com/golang/protobuf/proto"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/grpc-ecosystem/grpc-gateway/utilities"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
@@ -45,10 +45,7 @@ func request_GoCryptoTrader_GetExchanges_0(ctx context.Context, marshaler runtim
var protoReq GetExchangesRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchanges_0); err != nil {
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchanges_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -82,10 +79,7 @@ func request_GoCryptoTrader_GetExchangeInfo_0(ctx context.Context, marshaler run
var protoReq GenericExchangeNameRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeInfo_0); err != nil {
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeInfo_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -102,10 +96,7 @@ func request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler
var protoReq GenericExchangeNameRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil {
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetExchangeOTPCode_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -114,6 +105,15 @@ func request_GoCryptoTrader_GetExchangeOTPCode_0(ctx context.Context, marshaler
}
func request_GoCryptoTrader_GetExchangeOTPCodes_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetExchangeOTPsRequest
var metadata runtime.ServerMetadata
msg, err := client.GetExchangeOTPCodes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func request_GoCryptoTrader_EnableExchange_0(ctx context.Context, marshaler runtime.Marshaler, client GoCryptoTraderClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GenericExchangeNameRequest
var metadata runtime.ServerMetadata
@@ -191,10 +191,7 @@ func request_GoCryptoTrader_GetAccountInfo_0(ctx context.Context, marshaler runt
var protoReq GetAccountInfoRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_GoCryptoTrader_GetAccountInfo_0); err != nil {
if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_GoCryptoTrader_GetAccountInfo_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@@ -616,6 +613,26 @@ func RegisterGoCryptoTraderHandlerClient(ctx context.Context, mux *runtime.Serve
})
mux.Handle("GET", pattern_GoCryptoTrader_GetExchangeOTPCodes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_GoCryptoTrader_GetExchangeOTPCodes_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_GoCryptoTrader_GetExchangeOTPCodes_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_GoCryptoTrader_EnableExchange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -1130,6 +1147,8 @@ var (
pattern_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotp"}, ""))
pattern_GoCryptoTrader_GetExchangeOTPCodes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getexchangeotps"}, ""))
pattern_GoCryptoTrader_EnableExchange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "enableexchange"}, ""))
pattern_GoCryptoTrader_GetTicker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getticker"}, ""))
@@ -1192,6 +1211,8 @@ var (
forward_GoCryptoTrader_GetExchangeOTPCode_0 = runtime.ForwardResponseMessage
forward_GoCryptoTrader_GetExchangeOTPCodes_0 = runtime.ForwardResponseMessage
forward_GoCryptoTrader_EnableExchange_0 = runtime.ForwardResponseMessage
forward_GoCryptoTrader_GetTicker_0 = runtime.ForwardResponseMessage

View File

@@ -32,6 +32,12 @@ message GetExchangeOTPReponse {
string otp_code = 1;
}
message GetExchangeOTPsRequest {}
message GetExchangeOTPsResponse {
map<string, string> otp_codes = 1;
}
message DisableExchangeRequest {
string exchange = 1;
}
@@ -422,6 +428,12 @@ service GoCryptoTrader {
};
}
rpc GetExchangeOTPCodes (GetExchangeOTPsRequest) returns (GetExchangeOTPsResponse) {
option (google.api.http) = {
get: "/v1/getexchangeotps"
};
}
rpc EnableExchange (GenericExchangeNameRequest) returns (GenericExchangeNameResponse) {
option (google.api.http) = {
post: "/v1/enableexchange"

View File

@@ -327,6 +327,22 @@
]
}
},
"/v1/getexchangeotps": {
"get": {
"operationId": "GetExchangeOTPCodes",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/gctrpcGetExchangeOTPsResponse"
}
}
},
"tags": [
"GoCryptoTrader"
]
}
},
"/v1/getexchanges": {
"get": {
"operationId": "GetExchanges",
@@ -1104,6 +1120,17 @@
}
}
},
"gctrpcGetExchangeOTPsResponse": {
"type": "object",
"properties": {
"otp_codes": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"gctrpcGetExchangesResponse": {
"type": "object",
"properties": {