From f11f83fe5088418af22ff2dbac134c97e8e30866 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Wed, 26 Jul 2017 19:17:29 +1000 Subject: [PATCH] Formatting, linter and test for portfolio --- config/config.go | 2 +- events/event_test.go | 76 ++++++++++++---- events/events.go | 126 ++++++++++++++++---------- main.go | 4 +- portfolio/portfolio.go | 151 ++++++++++++++++++++++--------- portfolio/portfolio_test.go | 171 +++++++++++++++++++++++++++++++---- tools/portfolio/portfolio.go | 2 +- 7 files changed, 396 insertions(+), 136 deletions(-) diff --git a/config/config.go b/config/config.go index 61fc6128..211ebe5d 100644 --- a/config/config.go +++ b/config/config.go @@ -80,7 +80,7 @@ type Config struct { Name string EncryptConfig int Cryptocurrencies string - Portfolio portfolio.PortfolioBase `json:"PortfolioAddresses"` + Portfolio portfolio.Base `json:"PortfolioAddresses"` SMS SMSGlobalConfig `json:"SMSGlobal"` Webserver WebserverConfig `json:"Webserver"` Exchanges []ExchangeConfig `json:"Exchanges"` diff --git a/events/event_test.go b/events/event_test.go index 49f2f41e..e9b9070b 100644 --- a/events/event_test.go +++ b/events/event_test.go @@ -5,19 +5,19 @@ import ( ) func TestAddEvent(t *testing.T) { - eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST) + eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) if err != nil && eventID != 0 { t.Errorf("Test Failed. AddEvent: Error, %s", err) } - eventID, err = AddEvent("ANXX", "price", ">,==", "BTC", "LTC", ACTION_TEST) + eventID, err = AddEvent("ANXX", "price", ">,==", "BTC", "LTC", actionTest) if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Exchange") } - eventID, err = AddEvent("ANX", "prices", ">,==", "BTC", "LTC", ACTION_TEST) + eventID, err = AddEvent("ANX", "prices", ">,==", "BTC", "LTC", actionTest) if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Item") } - eventID, err = AddEvent("ANX", "price", "3===D", "BTC", "LTC", ACTION_TEST) + eventID, err = AddEvent("ANX", "price", "3===D", "BTC", "LTC", actionTest) if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Condition") } @@ -25,7 +25,7 @@ func TestAddEvent(t *testing.T) { if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Action") } - eventID, err = AddEvent("ANX", "price", ">,==", "BATMAN", "ROBIN", ACTION_TEST) + eventID, err = AddEvent("ANX", "price", ">,==", "BATMAN", "ROBIN", actionTest) if err == nil && eventID == 0 { t.Error("Test Failed. AddEvent: Error, error not captured in Action") } @@ -35,34 +35,38 @@ func TestAddEvent(t *testing.T) { } func TestRemoveEvent(t *testing.T) { - eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST) + eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) if err != nil && eventID != 0 { t.Errorf("Test Failed. RemoveEvent: Error, %s", err) } if !RemoveEvent(eventID) { t.Error("Test Failed. RemoveEvent: Error, error removing event") } + if RemoveEvent(1234) { + t.Error("Test Failed. RemoveEvent: Error, error removing event") + } } func TestGetEventCounter(t *testing.T) { - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST) + one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) if err != nil { t.Errorf("Test Failed. GetEventCounter: Error, %s", err) } - two, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST) + two, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) if err != nil { t.Errorf("Test Failed. GetEventCounter: Error, %s", err) } - three, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST) + three, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) if err != nil { t.Errorf("Test Failed. GetEventCounter: Error, %s", err) } + Events[three-1].Executed = true + total, _ := GetEventCounter() if total <= 0 { t.Errorf("Test Failed. GetEventCounter: Total = %d", total) } - if !RemoveEvent(one) { t.Error("Test Failed. GetEventCounter: Error, error removing event") } @@ -80,7 +84,7 @@ func TestGetEventCounter(t *testing.T) { } func TestExecuteAction(t *testing.T) { - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST) + one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) if err != nil { t.Errorf("Test Failed. ExecuteAction: Error, %s", err) } @@ -88,14 +92,15 @@ func TestExecuteAction(t *testing.T) { if !isExecuted { t.Error("Test Failed. ExecuteAction: Error, error removing event") } - if !RemoveEvent(one) { t.Error("Test Failed. ExecuteAction: Error, error removing event") } + + // More tests when ExecuteAction is expanded } func TestEventToString(t *testing.T) { - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST) + one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) if err != nil { t.Errorf("Test Failed. EventToString: Error, %s", err) } @@ -108,11 +113,10 @@ func TestEventToString(t *testing.T) { if !RemoveEvent(one) { t.Error("Test Failed. EventToString: Error, error removing event") } - } func TestCheckCondition(t *testing.T) { //error handling needs to be implemented - one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", ACTION_TEST) + one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", actionTest) if err != nil { t.Errorf("Test Failed. EventToString: Error, %s", err) } @@ -125,26 +129,58 @@ func TestCheckCondition(t *testing.T) { //error handling needs to be implemented if !RemoveEvent(one) { t.Error("Test Failed. EventToString: Error, error removing event") } - } func TestIsValidEvent(t *testing.T) { - err := IsValidEvent("ANX", "price", ">,==", ACTION_TEST) + err := IsValidEvent("ANX", "price", ">,==", actionTest) if err != nil { t.Errorf("Test Failed. IsValidExchange: Error %s", err) } + err = IsValidEvent("ANX", "price", ">,", actionTest) + if err == nil { + t.Errorf("Test Failed. IsValidExchange: Error") + } + err = IsValidEvent("ANX", "Testy", ">,==", actionTest) + if err == nil { + t.Errorf("Test Failed. IsValidExchange: Error") + } + err = IsValidEvent("Testys", "price", ">,==", actionTest) + if err == nil { + t.Errorf("Test Failed. IsValidExchange: Error") + } + + //Function tests need to appended to this function when more actions are + //implemented } func TestCheckEvents(t *testing.T) { //Add error handling //CheckEvents() //check once error handling is implemented } +func TestIsValidCurrency(t *testing.T) { + if !IsValidCurrency("BTC") { + t.Error("Test Failed - Event_test.go TestIsValidCurrency Error") + } + if !IsValidCurrency("USD") { + t.Error("Test Failed - Event_test.go TestIsValidCurrency Error") + } + if IsValidCurrency("testy") { + t.Error("Test Failed - Event_test.go TestIsValidCurrency Error") + } + if !IsValidCurrency("USD", "BTC", "USD") { + t.Error("Test Failed - Event_test.go TestIsValidCurrency Error") + } + if IsValidCurrency("USD", "USD", "Wigwham") { + t.Error("Test Failed - Event_test.go TestIsValidCurrency Error") + } +} + func TestIsValidExchange(t *testing.T) { - boolean := IsValidExchange("ANX", CONFIG_PATH_TEST) + boolean := IsValidExchange("ANX", configPathTest) if !boolean { t.Error("Test Failed. IsValidExchange: Error, incorrect Exchange") } - boolean = IsValidExchange("OBTUSE", CONFIG_PATH_TEST) + boolean = IsValidExchange("OBTUSE", configPathTest) if boolean { t.Error("Test Failed. IsValidExchange: Error, incorrect return") } @@ -182,7 +218,7 @@ func TestIsValidAction(t *testing.T) { if !boolean { t.Error("Test Failed. IsValidAction: Error, incorrect Action") } - boolean = IsValidAction(ACTION_TEST) + boolean = IsValidAction(actionTest) if !boolean { t.Error("Test Failed. IsValidAction: Error, incorrect Action") } diff --git a/events/events.go b/events/events.go index 891ac98a..5f3aa13e 100644 --- a/events/events.go +++ b/events/events.go @@ -15,26 +15,27 @@ import ( ) const ( - ITEM_PRICE = "PRICE" - GREATER_THAN = ">" - GREATER_THAN_OR_EQUAL = ">=" - LESS_THAN = "<" - LESS_THAN_OR_EQUAL = "<=" - IS_EQUAL = "==" - ACTION_SMS_NOTIFY = "SMS" - ACTION_CONSOLE_PRINT = "CONSOLE_PRINT" - ACTION_TEST = "ACTION_TEST" - CONFIG_PATH_TEST = config.ConfigTestFile + itemPrice = "PRICE" + greaterThan = ">" + greaterThanOrEqual = ">=" + lessThan = "<" + lessThanOrEqual = "<=" + isEqual = "==" + actionSMSNotify = "SMS" + actionConsolePrint = "CONSOLE_PRINT" + actionTest = "ACTION_TEST" + configPathTest = config.ConfigTestFile ) var ( - ErrInvalidItem = errors.New("Invalid item.") - ErrInvalidCondition = errors.New("Invalid conditional option.") - ErrInvalidAction = errors.New("Invalid action.") - ErrExchangeDisabled = errors.New("Desired exchange is disabled.") - ErrCurrencyInvalid = errors.New("Invalid currency.") + errInvalidItem = errors.New("invalid item") + errInvalidCondition = errors.New("invalid conditional option") + errInvalidAction = errors.New("invalid action") + errExchangeDisabled = errors.New("desired exchange is disabled") + errCurrencyInvalid = errors.New("invalid currency") ) +// Event struct holds the event variables type Event struct { ID int Exchange string @@ -46,8 +47,12 @@ type Event struct { Executed bool } +// Events variable is a pointer array to the event structures that will be +// appended var Events []*Event +// AddEvent adds an event to the Events chain and returns an index/eventID +// and an error func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Action string) (int, error) { err := IsValidEvent(Exchange, Item, Condition, Action) if err != nil { @@ -55,7 +60,7 @@ func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Action s } if !IsValidCurrency(FirstCurrency, SecondCurrency) { - return 0, ErrCurrencyInvalid + return 0, errCurrencyInvalid } Event := &Event{} @@ -77,6 +82,7 @@ func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Action s return Event.ID, nil } +// RemoveEvent deletes and event by its ID func RemoveEvent(EventID int) bool { for i, x := range Events { if x.ID == EventID { @@ -87,6 +93,8 @@ func RemoveEvent(EventID int) bool { return false } +// GetEventCounter displays the emount of total events on the chain and the +// events that have been executed. func GetEventCounter() (int, int) { total := len(Events) executed := 0 @@ -99,15 +107,18 @@ func GetEventCounter() (int, int) { return total, executed } +// ExecuteAction will execute the action pending on the chain func (e *Event) ExecuteAction() bool { if common.StringContains(e.Action, ",") { action := common.SplitStrings(e.Action, ",") - if action[0] == ACTION_SMS_NOTIFY { + if action[0] == actionSMSNotify { message := fmt.Sprintf("Event triggered: %s", e.EventToString()) if action[1] == "ALL" { smsglobal.SMSSendToAll(message, config.Cfg) } else { - smsglobal.SMSNotify(smsglobal.SMSGetNumberByName(action[1], config.Cfg.SMS), message, config.Cfg) + smsglobal.SMSNotify(smsglobal.SMSGetNumberByName(action[1], + config.Cfg.SMS), message, config.Cfg, + ) } } } else { @@ -116,13 +127,18 @@ func (e *Event) ExecuteAction() bool { return true } +// EventToString turns the structure event into a string func (e *Event) EventToString() string { condition := common.SplitStrings(e.Condition, ",") - return fmt.Sprintf("If the %s%s %s on %s is %s then %s.", e.FirstCurrency, e.SecondCurrency, e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action) + return fmt.Sprintf( + "If the %s%s %s on %s is %s then %s.", e.FirstCurrency, e.SecondCurrency, + e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action, + ) } -func (e *Event) CheckCondition() bool { //Add error handling - lastPrice := 0.00 +// CheckCondition will check the event structure to see if there is a condition +// met +func (e *Event) CheckCondition() bool { condition := common.SplitStrings(e.Condition, ",") targetPrice, _ := strconv.ParseFloat(condition[1], 64) @@ -131,38 +147,38 @@ func (e *Event) CheckCondition() bool { //Add error handling return false } - lastPrice = ticker.Price[pair.CurrencyItem(e.FirstCurrency)][pair.CurrencyItem(e.SecondCurrency)].Last + lastPrice := ticker.Price[pair.CurrencyItem(e.FirstCurrency)][pair.CurrencyItem(e.SecondCurrency)].Last if lastPrice == 0 { return false } switch condition[0] { - case GREATER_THAN: + case greaterThan: { if lastPrice > targetPrice { return e.ExecuteAction() } } - case GREATER_THAN_OR_EQUAL: + case greaterThanOrEqual: { if lastPrice >= targetPrice { return e.ExecuteAction() } } - case LESS_THAN: + case lessThan: { if lastPrice < targetPrice { return e.ExecuteAction() } } - case LESS_THAN_OR_EQUAL: + case lessThanOrEqual: { if lastPrice <= targetPrice { return e.ExecuteAction() } } - case IS_EQUAL: + case isEqual: { if lastPrice == targetPrice { return e.ExecuteAction() @@ -172,52 +188,56 @@ func (e *Event) CheckCondition() bool { //Add error handling return false } +// IsValidEvent checks the actions to be taken and returns an error if incorrect func IsValidEvent(Exchange, Item, Condition, Action string) error { Exchange = common.StringToUpper(Exchange) Item = common.StringToUpper(Item) Action = common.StringToUpper(Action) configPath := "" - if Action == ACTION_TEST { - configPath = CONFIG_PATH_TEST + if Action == actionTest { + configPath = configPathTest } if !IsValidExchange(Exchange, configPath) { - return ErrExchangeDisabled + return errExchangeDisabled } if !IsValidItem(Item) { - return ErrInvalidItem + return errInvalidItem } if !common.StringContains(Condition, ",") { - return ErrInvalidCondition + return errInvalidCondition } condition := common.SplitStrings(Condition, ",") if !IsValidCondition(condition[0]) || len(condition[1]) == 0 { - return ErrInvalidCondition + return errInvalidCondition } if common.StringContains(Action, ",") { action := common.SplitStrings(Action, ",") - if action[0] != ACTION_SMS_NOTIFY { - return ErrInvalidAction + if action[0] != actionSMSNotify { + return errInvalidAction } - if action[1] != "ALL" && smsglobal.SMSGetNumberByName(action[1], config.Cfg.SMS) == smsglobal.ErrSMSContactNotFound { - return ErrInvalidAction + if action[1] != "ALL" && smsglobal.SMSGetNumberByName( + action[1], config.Cfg.SMS) == smsglobal.ErrSMSContactNotFound { + return errInvalidAction } } else { - if Action != ACTION_CONSOLE_PRINT && Action != ACTION_TEST { - return ErrInvalidAction + if Action != actionConsolePrint && Action != actionTest { + return errInvalidAction } } return nil } +// CheckEvents is the overarching routine that will iterate through the Events +// chain func CheckEvents() { for { total, executed := GetEventCounter() @@ -226,7 +246,10 @@ func CheckEvents() { if !event.Executed { success := event.CheckCondition() if success { - log.Printf("Event %d triggered on %s successfully.\n", event.ID, event.Exchange) + log.Printf( + "Event %d triggered on %s successfully.\n", event.ID, + event.Exchange, + ) event.Executed = true } } @@ -235,19 +258,21 @@ func CheckEvents() { } } +// IsValidCurrency takes in CRYPTO or FIAT currency strings and returns if valid func IsValidCurrency(currencies ...string) bool { - for _, whatIsIt := range currencies { + for index, whatIsIt := range currencies { whatIsIt = common.StringToUpper(whatIsIt) - if currency.IsDefaultCryptocurrency(whatIsIt) { - return true - } - if currency.IsDefaultCurrency(whatIsIt) { - return true + if currency.IsDefaultCryptocurrency(whatIsIt) || currency.IsDefaultCurrency(whatIsIt) { + if len(currencies)-1 == index { + return true + } + continue } } return false } +// IsValidExchange validates the exchange func IsValidExchange(Exchange, configPath string) bool { Exchange = common.StringToUpper(Exchange) @@ -264,27 +289,30 @@ func IsValidExchange(Exchange, configPath string) bool { return false } +// IsValidCondition validates passed in condition func IsValidCondition(Condition string) bool { switch Condition { - case GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, IS_EQUAL: + case greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, isEqual: return true } return false } +// IsValidAction validates passed in action func IsValidAction(Action string) bool { Action = common.StringToUpper(Action) switch Action { - case ACTION_SMS_NOTIFY, ACTION_CONSOLE_PRINT, ACTION_TEST: + case actionSMSNotify, actionConsolePrint, actionTest: return true } return false } +// IsValidItem validates passed in Item func IsValidItem(Item string) bool { Item = common.StringToUpper(Item) switch Item { - case ITEM_PRICE: + case itemPrice: return true } return false diff --git a/main.go b/main.go index 61e6446a..e4d5a4b3 100644 --- a/main.go +++ b/main.go @@ -58,7 +58,7 @@ type ExchangeMain struct { type Bot struct { config *config.Config - portfolio *portfolio.PortfolioBase + portfolio *portfolio.Base exchange ExchangeMain exchanges []exchange.IBotExchange tickers []ticker.Ticker @@ -242,7 +242,7 @@ func SeedExchangeAccountInfo(data []exchange.ExchangeAccountInfo) { } if !port.ExchangeAddressExists(exchangeName, currencyName) { - port.Addresses = append(port.Addresses, portfolio.PortfolioAddress{Address: exchangeName, CoinType: currencyName, Balance: total, Decscription: portfolio.PORTFOLIO_ADDRESS_EXCHANGE}) + port.Addresses = append(port.Addresses, portfolio.Address{Address: exchangeName, CoinType: currencyName, Balance: total, Decscription: portfolio.PortfolioAddressExchange}) } else { port.UpdateExchangeAddressBalance(exchangeName, currencyName, total) } diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index deb11930..5b5f9a51 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -10,35 +10,43 @@ import ( ) const ( - BLOCKR_API_URL = "blockr.io/api" - BLOCKR_API_VERSION = "1" - BLOCKR_ADDRESS_BALANCE = "address/balance" + blockrAPIURL = "blockr.io/api" + blockrAPIVersion = "1" + blockrAddressBalance = "address/balance" - ETHERCHAIN_API_URL = "https://etherchain.org/api" - ETHERCHAIN_ACCOUNT_MULTIPLE = "account/multiple" - PORTFOLIO_ADDRESS_EXCHANGE = "Exchange" - PORTFOLIO_ADDRESS_PERSONAL = "Personal" + etherchainAPIURL = "https://etherchain.org/api" + etherchainAccountMultiple = "account/multiple" + // PortfolioAddressExchange holds the current portfolio address + PortfolioAddressExchange = "Exchange" + portfolioAddressPersonal = "Personal" ) -var Portfolio PortfolioBase +// Portfolio is variable store holding an array of portfolioAddress +var Portfolio Base -type PortfolioAddress struct { +// Base holds the portfolio base addresses +type Base struct { + Addresses []Address +} + +// Address sub type holding address information for portfolio +type Address struct { Address string CoinType string Balance float64 Decscription string } -type PortfolioBase struct { - Addresses []PortfolioAddress -} - +// BlockrAddress holds JSON incoming and outgoing data for BLOCKR with address +// information type BlockrAddress struct { Address string `json:"address"` Balance float64 `json:"balance"` BalanceMultisig float64 `json:"balance_multisig"` } +// BlockrAddressBalanceSingle holds JSON incoming and outgoing data for BLOCKR +// with address balance information type BlockrAddressBalanceSingle struct { Status string `json:"status"` Data BlockrAddress `json:"data"` @@ -46,6 +54,8 @@ type BlockrAddressBalanceSingle struct { Message string `json:"message"` } +// BlockrAddressBalanceMulti holds JSON incoming and outgoing data for BLOCKR +// with address balance information for multiple wallets type BlockrAddressBalanceMulti struct { Status string `json:"status"` Data []BlockrAddress `json:"data"` @@ -53,6 +63,8 @@ type BlockrAddressBalanceMulti struct { Message string `json:"message"` } +// EtherchainBalanceResponse holds JSON incoming and outgoing data for +// Etherchain type EtherchainBalanceResponse struct { Status int `json:"status"` Data []struct { @@ -66,7 +78,8 @@ type EtherchainBalanceResponse struct { } `json:"data"` } -// ExchangeAccountInfo : Generic type to hold each exchange's holdings in all enabled currencies +// ExchangeAccountInfo : Generic type to hold each exchange's holdings in all +// enabled currencies type ExchangeAccountInfo struct { ExchangeName string Currencies []ExchangeAccountCurrencyInfo @@ -79,6 +92,8 @@ type ExchangeAccountCurrencyInfo struct { Hold float64 } +// GetEthereumBalance single or multiple address information as +// EtherchainBalanceResponse func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { for _, add := range address { valid, _ := common.IsValidCryptoAddress(add, "eth") @@ -88,7 +103,9 @@ func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { } addresses := common.JoinStrings(address, ",") - url := fmt.Sprintf("%s/%s/%s", ETHERCHAIN_API_URL, ETHERCHAIN_ACCOUNT_MULTIPLE, addresses) + url := fmt.Sprintf( + "%s/%s/%s", etherchainAPIURL, etherchainAccountMultiple, addresses, + ) result := EtherchainBalanceResponse{} err := common.SendHTTPGetRequest(url, true, &result) if err != nil { @@ -100,13 +117,20 @@ func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { return result, nil } +// GetBlockrBalanceSingle queries Blockr for an address balance for either a +// LTC or a BTC single address func GetBlockrBalanceSingle(address string, coinType string) (BlockrAddressBalanceSingle, error) { valid, _ := common.IsValidCryptoAddress(address, coinType) if !valid { - return BlockrAddressBalanceSingle{}, errors.New(fmt.Sprintf("Not a %s address", common.StringToUpper(coinType))) + return BlockrAddressBalanceSingle{}, fmt.Errorf( + "Not a %s address", common.StringToUpper(coinType), + ) } - url := fmt.Sprintf("https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), BLOCKR_API_URL, BLOCKR_API_VERSION, BLOCKR_ADDRESS_BALANCE, address) + url := fmt.Sprintf( + "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, + blockrAPIVersion, blockrAddressBalance, address, + ) result := BlockrAddressBalanceSingle{} err := common.SendHTTPGetRequest(url, true, &result) if err != nil { @@ -118,15 +142,22 @@ func GetBlockrBalanceSingle(address string, coinType string) (BlockrAddressBalan return result, nil } +// GetBlockrAddressMulti queries Blockr for an address balance for either a LTC +// or a BTC multiple addresses func GetBlockrAddressMulti(addresses []string, coinType string) (BlockrAddressBalanceMulti, error) { for _, add := range addresses { valid, _ := common.IsValidCryptoAddress(add, coinType) if !valid { - return BlockrAddressBalanceMulti{}, errors.New(fmt.Sprintf("Not a %s address", common.StringToUpper(coinType))) + return BlockrAddressBalanceMulti{}, fmt.Errorf( + "Not a %s address", common.StringToUpper(coinType), + ) } } addressesStr := common.JoinStrings(addresses, ",") - url := fmt.Sprintf("https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), BLOCKR_API_URL, BLOCKR_API_VERSION, BLOCKR_ADDRESS_BALANCE, addressesStr) + url := fmt.Sprintf( + "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, + blockrAPIVersion, blockrAddressBalance, addressesStr, + ) result := BlockrAddressBalanceMulti{} err := common.SendHTTPGetRequest(url, true, &result) if err != nil { @@ -138,7 +169,9 @@ func GetBlockrAddressMulti(addresses []string, coinType string) (BlockrAddressBa return result, nil } -func (p *PortfolioBase) GetAddressBalance(address string) (float64, bool) { +// GetAddressBalance acceses the portfolio base and returns the balance by passed +// in address +func (p *Base) GetAddressBalance(address string) (float64, bool) { for _, x := range p.Addresses { if x.Address == address { return x.Balance, true @@ -147,7 +180,8 @@ func (p *PortfolioBase) GetAddressBalance(address string) (float64, bool) { return 0, false } -func (p *PortfolioBase) ExchangeExists(exchangeName string) bool { +// ExchangeExists checks to see if an exchange exists in the portfolio base +func (p *Base) ExchangeExists(exchangeName string) bool { for _, x := range p.Addresses { if x.Address == exchangeName { return true @@ -156,7 +190,9 @@ func (p *PortfolioBase) ExchangeExists(exchangeName string) bool { return false } -func (p *PortfolioBase) AddressExists(address string) bool { +// AddressExists checks to see if there is an address associated with the +// portfolio base +func (p *Base) AddressExists(address string) bool { for _, x := range p.Addresses { if x.Address == address { return true @@ -165,7 +201,9 @@ func (p *PortfolioBase) AddressExists(address string) bool { return false } -func (p *PortfolioBase) ExchangeAddressExists(exchangeName, coinType string) bool { +// ExchangeAddressExists checks to see if there is an exchange address +// associated with the portfolio base +func (p *Base) ExchangeAddressExists(exchangeName, coinType string) bool { for _, x := range p.Addresses { if x.Address == exchangeName && x.CoinType == coinType { return true @@ -174,32 +212,40 @@ func (p *PortfolioBase) ExchangeAddressExists(exchangeName, coinType string) boo return false } -func (p *PortfolioBase) UpdateAddressBalance(address string, amount float64) { - for x, _ := range p.Addresses { +// UpdateAddressBalance updates the portfolio base balance +func (p *Base) UpdateAddressBalance(address string, amount float64) { + for x := range p.Addresses { if p.Addresses[x].Address == address { p.Addresses[x].Balance = amount } } } -func (p *PortfolioBase) UpdateExchangeAddressBalance(exchangeName, coinType string, balance float64) { - for x, _ := range p.Addresses { +// UpdateExchangeAddressBalance updates the portfolio balance when checked +// against correct exchangeName and coinType. +func (p *Base) UpdateExchangeAddressBalance(exchangeName, coinType string, balance float64) { + for x := range p.Addresses { if p.Addresses[x].Address == exchangeName && p.Addresses[x].CoinType == coinType { p.Addresses[x].Balance = balance } } } -func (p *PortfolioBase) AddAddress(address, coinType, description string, balance float64) { +// AddAddress adds an address to the portfolio base +func (p *Base) AddAddress(address, coinType, description string, balance float64) { if !p.AddressExists(address) { - p.Addresses = append(p.Addresses, PortfolioAddress{Address: address, CoinType: coinType, Balance: balance, Decscription: description}) + p.Addresses = append( + p.Addresses, Address{Address: address, CoinType: coinType, + Balance: balance, Decscription: description}, + ) } else { p.UpdateAddressBalance(address, balance) } } -func (p *PortfolioBase) UpdatePortfolio(addresses []string, coinType string) bool { - if common.StringContains(common.JoinStrings(addresses, ","), PORTFOLIO_ADDRESS_EXCHANGE) || common.StringContains(common.JoinStrings(addresses, ","), PORTFOLIO_ADDRESS_PERSONAL) { +// UpdatePortfolio adds to the portfolio addresses by coin type +func (p *Base) UpdatePortfolio(addresses []string, coinType string) bool { + if common.StringContains(common.JoinStrings(addresses, ","), PortfolioAddressExchange) || common.StringContains(common.JoinStrings(addresses, ","), portfolioAddressPersonal) { return true } @@ -210,7 +256,7 @@ func (p *PortfolioBase) UpdatePortfolio(addresses []string, coinType string) boo } for _, x := range result.Data { - p.AddAddress(x.Address, coinType, PORTFOLIO_ADDRESS_PERSONAL, x.Balance) + p.AddAddress(x.Address, coinType, portfolioAddressPersonal, x.Balance) } return true } @@ -220,22 +266,25 @@ func (p *PortfolioBase) UpdatePortfolio(addresses []string, coinType string) boo return false } for _, x := range result.Data { - p.AddAddress(x.Address, coinType, PORTFOLIO_ADDRESS_PERSONAL, x.Balance) + p.AddAddress(x.Address, coinType, portfolioAddressPersonal, x.Balance) } } else { result, err := GetBlockrBalanceSingle(addresses[0], coinType) if err != nil { return false } - p.AddAddress(addresses[0], coinType, PORTFOLIO_ADDRESS_PERSONAL, result.Data.Balance) + p.AddAddress( + addresses[0], coinType, portfolioAddressPersonal, result.Data.Balance, + ) } return true } -func (p *PortfolioBase) GetExchangePortfolio() map[string]float64 { +// GetExchangePortfolio returns current portfolio base information +func (p *Base) GetExchangePortfolio() map[string]float64 { result := make(map[string]float64) for _, x := range p.Addresses { - if x.Decscription != PORTFOLIO_ADDRESS_EXCHANGE { + if x.Decscription != PortfolioAddressExchange { continue } balance, ok := result[x.CoinType] @@ -248,10 +297,11 @@ func (p *PortfolioBase) GetExchangePortfolio() map[string]float64 { return result } -func (p *PortfolioBase) GetPersonalPortfolio() map[string]float64 { +// GetPersonalPortfolio returns current portfolio base information +func (p *Base) GetPersonalPortfolio() map[string]float64 { result := make(map[string]float64) for _, x := range p.Addresses { - if x.Decscription == PORTFOLIO_ADDRESS_EXCHANGE { + if x.Decscription == PortfolioAddressExchange { continue } balance, ok := result[x.CoinType] @@ -264,7 +314,8 @@ func (p *PortfolioBase) GetPersonalPortfolio() map[string]float64 { return result } -func (p *PortfolioBase) GetPortfolioSummary(coinFilter string) map[string]float64 { +// GetPortfolioSummary rpoves a summary for your portfolio base +func (p *Base) GetPortfolioSummary(coinFilter string) map[string]float64 { result := make(map[string]float64) for _, x := range p.Addresses { if coinFilter != "" && coinFilter != x.CoinType { @@ -280,10 +331,11 @@ func (p *PortfolioBase) GetPortfolioSummary(coinFilter string) map[string]float6 return result } -func (p *PortfolioBase) GetPortfolioGroupedCoin() map[string][]string { +// GetPortfolioGroupedCoin returns portfolio base information grouped by coin +func (p *Base) GetPortfolioGroupedCoin() map[string][]string { result := make(map[string][]string) for _, x := range p.Addresses { - if common.StringContains(x.Decscription, PORTFOLIO_ADDRESS_EXCHANGE) || common.StringContains(x.Decscription, PORTFOLIO_ADDRESS_PERSONAL) { + if common.StringContains(x.Decscription, PortfolioAddressExchange) || common.StringContains(x.Decscription, portfolioAddressPersonal) { continue } result[x.CoinType] = append(result[x.CoinType], x.Address) @@ -291,25 +343,34 @@ func (p *PortfolioBase) GetPortfolioGroupedCoin() map[string][]string { return result } -func (p *PortfolioBase) SeedPortfolio(port PortfolioBase) { +// SeedPortfolio appends a portfolio base object with another base portfolio +// addresses +func (p *Base) SeedPortfolio(port Base) { p.Addresses = port.Addresses } +// StartPortfolioWatcher observes the portfolio object func StartPortfolioWatcher() { addrCount := len(Portfolio.Addresses) - log.Printf("PortfolioWatcher started: Have %d entries in portfolio.\n", addrCount) + log.Printf( + "PortfolioWatcher started: Have %d entries in portfolio.\n", addrCount, + ) for { data := Portfolio.GetPortfolioGroupedCoin() for key, value := range data { success := Portfolio.UpdatePortfolio(value, key) if success { - log.Printf("PortfolioWatcher: Successfully updated address balance for %s address(es) %s\n", key, value) + log.Printf( + "PortfolioWatcher: Successfully updated address balance for %s address(es) %s\n", + key, value, + ) } } time.Sleep(time.Minute * 10) } } -func GetPortfolio() *PortfolioBase { +// GetPortfolio returns a pointer to the portfolio base +func GetPortfolio() *Base { return &Portfolio } diff --git a/portfolio/portfolio_test.go b/portfolio/portfolio_test.go index 03bc3b00..c815a5ab 100644 --- a/portfolio/portfolio_test.go +++ b/portfolio/portfolio_test.go @@ -1,20 +1,26 @@ package portfolio import ( + "reflect" "testing" ) func TestGetEthereumBalance(t *testing.T) { addresses := []string{"0xb794f5ea0ba39494ce839613fffba74279579268", "0xe853c56864a2ebe4576a807d26fdc4a0ada51919"} - nonsenseAddress := []string{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, 0xe853c56864a2ebe4576a807d26fdc4a0ada51919"} + nonsenseAddress := []string{ + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "0xe853c56864a2ebe4576a807d26fdc4a0ada51919", + } response, err := GetEthereumBalance(addresses) if err != nil { t.Errorf("Test Failed - Portfolio GetEthereumBalance() Error: %s", err) } if len(response.Data) != 2 { - t.Error("Test Failed - Portfolio GetEthereumBalance() Error: Incorrect address") + t.Error( + "Test Failed - Portfolio GetEthereumBalance() Error: Incorrect address", + ) } response, err = GetEthereumBalance(nonsenseAddress) @@ -54,13 +60,19 @@ func TestGetBlockrBalanceSingle(t *testing.T) { t.Errorf("Test Failed - Portfolio GetBlockrBalanceSingle() Error: %s", err) } if response.Status == "success" { - t.Error("Test Failed - Portfolio GetBlockrBalanceSingle() Error: Incorrect status") + t.Error( + "Test Failed - Portfolio GetBlockrBalanceSingle() Error: Incorrect status", + ) } } func TestGetBlockrAddressMulti(t *testing.T) { - litecoinAddresses := []string{"LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL", "LVa8wZ983PvWtdwXZ8viK6SocMENLCXkEy"} - bitcoinAddresses := []string{"3D2oetdNuZUqQHPJmcMDDHYoqkyNVsFk9r", "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v"} + litecoinAddresses := []string{ + "LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL", "LVa8wZ983PvWtdwXZ8viK6SocMENLCXkEy", + } + bitcoinAddresses := []string{ + "3D2oetdNuZUqQHPJmcMDDHYoqkyNVsFk9r", "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", + } nonsenseAddresses := []string{"DingDong", "ningNang"} ltc := "LtC" btc := "bTc" @@ -85,7 +97,7 @@ func TestGetAddressBalance(t *testing.T) { description := "Description of Wallet" balance := float64(1000) - portfolio := PortfolioBase{} + portfolio := Base{} portfolio.AddAddress(ltcAddress, ltc, description, balance) addBalance, _ := portfolio.GetAddressBalance("LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL") @@ -102,59 +114,182 @@ func TestGetAddressBalance(t *testing.T) { } } -// func TestExchangeExists(t *testing.T) { -// portfolio := PortfolioBase{} -// portfolio.SeedPortfolio(port) -// } +func TestExchangeExists(t *testing.T) { + newBase := Base{} + newBase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + if !newBase.ExchangeExists("someaddress") { + t.Error("Test Failed - portfolio_test.go - AddressExists error") + } + if newBase.ExchangeExists("bla") { + t.Error("Test Failed - portfolio_test.go - AddressExists error") + } +} func TestAddressExists(t *testing.T) { - + newbase := Base{} + newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + if !newbase.AddressExists("someaddress") { + t.Error("Test Failed - portfolio_test.go - AddressExists error") + } + if newbase.AddressExists("bla") { + t.Error("Test Failed - portfolio_test.go - AddressExists error") + } } func TestExchangeAddressExists(t *testing.T) { + newbase := Base{} + newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + if !newbase.ExchangeAddressExists("someaddress", "LTC") { + t.Error("Test Failed - portfolio_test.go - ExchangeAddressExists error") + } + if newbase.ExchangeAddressExists("TEST", "LTC") { + t.Error("Test Failed - portfolio_test.go - ExchangeAddressExists error") + } } func TestUpdateAddressBalance(t *testing.T) { + newbase := Base{} + newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + newbase.UpdateAddressBalance("someaddress", 0.03) + value := newbase.GetPortfolioSummary("LTC") + if value["LTC"] != 0.03 { + t.Error("Test Failed - portfolio_test.go - UpdateUpdateAddressBalance error") + } } func TestUpdateExchangeAddressBalance(t *testing.T) { + newbase := Base{} + newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + portfolio := GetPortfolio() + portfolio.SeedPortfolio(newbase) + portfolio.UpdateExchangeAddressBalance("someaddress", "LTC", 0.04) + value := portfolio.GetPortfolioSummary("LTC") + if value["LTC"] != 0.04 { + t.Error("Test Failed - portfolio_test.go - UpdateExchangeAddressBalance error") + } } func TestAddAddress(t *testing.T) { - + newbase := Base{} + newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + portfolio := GetPortfolio() + portfolio.SeedPortfolio(newbase) + if !portfolio.AddressExists("someaddress") { + t.Error("Test Failed - portfolio_test.go - AddAddress error") + } } func TestUpdatePortfolio(t *testing.T) { + newbase := Base{} + newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + portfolio := GetPortfolio() + portfolio.SeedPortfolio(newbase) + value := portfolio.UpdatePortfolio( + []string{"LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL"}, "LTC", + ) + if !value { + t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + } + value = portfolio.UpdatePortfolio([]string{"Testy"}, "LTC") + if value { + t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + } + value = portfolio.UpdatePortfolio( + []string{"LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL", "LVa8wZ983PvWtdwXZ8viK6SocMENLCXkEy"}, + "LTC", + ) + if !value { + t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + } + value = portfolio.UpdatePortfolio( + []string{"LdP8Qox1VAhCzLJNqrr74YovaWYyNBUWvL", "Testy"}, "LTC", + ) + if value { + t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + } + value = portfolio.UpdatePortfolio( + []string{"0xb794f5ea0ba39494ce839613fffba74279579268", + "0xe853c56864a2ebe4576a807d26fdc4a0ada51919"}, "ETH", + ) + if !value { + t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + } + value = portfolio.UpdatePortfolio( + []string{"0xb794f5ea0ba39494ce839613fffba74279579268", "TESTY"}, "ETH", + ) + if value { + t.Error("Test Failed - portfolio_test.go - UpdatePortfolio error") + } } func TestGetExchangePortfolio(t *testing.T) { - + newbase := Base{} + newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + portfolio := GetPortfolio() + portfolio.SeedPortfolio(newbase) + value := portfolio.GetExchangePortfolio() + _, ok := value["ANX"] + if ok { + t.Error("Test Failed - portfolio_test.go - GetExchangePortfolio error") + } } func TestGetPersonalPortfolio(t *testing.T) { - + newbase := Base{} + newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + portfolio := GetPortfolio() + portfolio.SeedPortfolio(newbase) + value := portfolio.GetPersonalPortfolio() + _, ok := value["LTC"] + if !ok { + t.Error("Test Failed - portfolio_test.go - GetPersonalPortfolio error") + } } func TestGetPortfolioSummary(t *testing.T) { - + newbase := Base{} + newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + portfolio := GetPortfolio() + portfolio.SeedPortfolio(newbase) + value := portfolio.GetPortfolioSummary("LTC") + if value["LTC"] != 0.02 { + t.Error("Test Failed - portfolio_test.go - GetPortfolioGroupedCoin error") + } } func TestGetPortfolioGroupedCoin(t *testing.T) { - + newbase := Base{} + newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + portfolio := GetPortfolio() + portfolio.SeedPortfolio(newbase) + value := portfolio.GetPortfolioGroupedCoin() + if value["LTC"][0] != "someaddress" { + t.Error("Test Failed - portfolio_test.go - GetPortfolioGroupedCoin error") + } } func TestSeedPortfolio(t *testing.T) { + newbase := Base{} + newbase.AddAddress("someaddress", "LTC", "LTCWALLETTEST", 0.02) + portfolio := GetPortfolio() + portfolio.SeedPortfolio(newbase) + if !portfolio.AddressExists("someaddress") { + t.Error("Test Failed - portfolio_test.go - SeedPortfolio error") + } } func TestStartPortfolioWatcher(t *testing.T) { - + //Not until testTimeoutFeature and errors } func TestGetPortfolio(t *testing.T) { - + ptrBASE := GetPortfolio() + if reflect.TypeOf(ptrBASE).String() != "*portfolio.Base" { + t.Error("Test Failed - portfolio_test.go - GetoPortfolio error") + } } diff --git a/tools/portfolio/portfolio.go b/tools/portfolio/portfolio.go index 0e783335..3df8cf1a 100644 --- a/tools/portfolio/portfolio.go +++ b/tools/portfolio/portfolio.go @@ -48,7 +48,7 @@ func main() { log.Fatal("File isn't in JSON format") } - port := portfolio.PortfolioBase{} + port := portfolio.Base{} port.SeedPortfolio(cfg.Portfolio) result := port.GetPortfolioSummary("")