mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-09-08 02:20:08 +00:00
fix(coin): accept symbol-first trades
This commit is contained in:
@@ -70,16 +70,17 @@ func (s *state) handleBuy(ctx context.Context, b *bot.Bot, update *models.Update
|
||||
}
|
||||
args := argsAfterCommand(update.Message.Text)
|
||||
if len(args) != 2 {
|
||||
return chathelper.Reply(ctx, b, update.Message, "Usage: /coin_buy <usd_amount> <COIN>\nExample: /coin_buy 100 BTC")
|
||||
return chathelper.Reply(ctx, b, update.Message, "Usage: /coin_buy <COIN> <usd_amount>\nExample: /coin_buy BTC 10")
|
||||
}
|
||||
amount, ok := parsePositiveFinite(args[0])
|
||||
if !ok || !isSafeUSD(amount) {
|
||||
parsed, err := parseCoinValueArgs(args, isSafeUSD, errInvalidUSDAmount)
|
||||
if errors.Is(err, errInvalidUSDAmount) {
|
||||
return chathelper.Reply(ctx, b, update.Message, "USD amount must be a positive finite number within the supported range.")
|
||||
}
|
||||
coin, err := ResolveCoinSymbol(args[1])
|
||||
if err != nil {
|
||||
return s.replyPriceError(ctx, b, update, err)
|
||||
}
|
||||
coin := parsed.coin
|
||||
amount := parsed.value
|
||||
price, err := s.prices.FetchUSD(ctx, coin)
|
||||
if err != nil {
|
||||
return s.replyPriceError(ctx, b, update, err)
|
||||
@@ -120,16 +121,17 @@ func (s *state) handleSell(ctx context.Context, b *bot.Bot, update *models.Updat
|
||||
}
|
||||
args := argsAfterCommand(update.Message.Text)
|
||||
if len(args) != 2 {
|
||||
return chathelper.Reply(ctx, b, update.Message, "Usage: /coin_sell <qty> <COIN>\nExample: /coin_sell 0.01 BTC")
|
||||
return chathelper.Reply(ctx, b, update.Message, "Usage: /coin_sell <COIN> <qty>\nExample: /coin_sell BTC 0.01")
|
||||
}
|
||||
qty, ok := parsePositiveFinite(args[0])
|
||||
if !ok {
|
||||
parsed, err := parseCoinValueArgs(args, isPositiveFinite, errInvalidQuantity)
|
||||
if errors.Is(err, errInvalidQuantity) {
|
||||
return chathelper.Reply(ctx, b, update.Message, "Quantity must be a positive finite number.")
|
||||
}
|
||||
coin, err := ResolveCoinSymbol(args[1])
|
||||
if err != nil {
|
||||
return s.replyPriceError(ctx, b, update, err)
|
||||
}
|
||||
coin := parsed.coin
|
||||
qty := parsed.value
|
||||
price, err := s.prices.FetchUSD(ctx, coin)
|
||||
if err != nil {
|
||||
return s.replyPriceError(ctx, b, update, err)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package coin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/tiennm99/miti99bot/internal/testutil"
|
||||
)
|
||||
|
||||
func TestHandleBuyAcceptsCoinFirstOrder(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
s := newTestState(map[string]CoinPrice{"BTC": {USD: 50000, Source: "Binance"}}, nil)
|
||||
rb := testutil.NewRecordingBot(t)
|
||||
_ = s.handleTopup(ctx, rb.Bot, testutil.NewPrivateMessage(7, "/coin_topup 1000"))
|
||||
rb.Reset()
|
||||
|
||||
if err := s.handleBuy(ctx, rb.Bot, testutil.NewPrivateMessage(7, "/coin_buy BTC 10")); err != nil {
|
||||
t.Fatalf("handleBuy: %v", err)
|
||||
}
|
||||
|
||||
rb.AssertSentText(t, "Bought 0.0002 BTC")
|
||||
p, _ := LoadPortfolio(ctx, s.kv, 7, 999)
|
||||
if p.USD != 990 || p.Assets["BTC"] != 0.0002 {
|
||||
t.Fatalf("after coin-first buy = %+v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSellAcceptsCoinFirstOrder(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
s := newTestState(map[string]CoinPrice{"BTC": {USD: 50000, Source: "Binance"}}, nil)
|
||||
rb := testutil.NewRecordingBot(t)
|
||||
_ = s.handleTopup(ctx, rb.Bot, testutil.NewPrivateMessage(7, "/coin_topup 1000"))
|
||||
_ = s.handleBuy(ctx, rb.Bot, testutil.NewPrivateMessage(7, "/coin_buy 500 BTC"))
|
||||
rb.Reset()
|
||||
|
||||
if err := s.handleSell(ctx, rb.Bot, testutil.NewPrivateMessage(7, "/coin_sell BTC 0.01")); err != nil {
|
||||
t.Fatalf("handleSell: %v", err)
|
||||
}
|
||||
|
||||
rb.AssertSentText(t, "Sold 0.01 BTC")
|
||||
p, _ := LoadPortfolio(ctx, s.kv, 7, 999)
|
||||
if p.USD != 1000 || len(p.Assets) != 0 {
|
||||
t.Fatalf("after coin-first sell = %+v", p)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package coin
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errInvalidUSDAmount = errors.New("coin: invalid USD amount")
|
||||
errInvalidQuantity = errors.New("coin: invalid quantity")
|
||||
)
|
||||
|
||||
type coinValueArgs struct {
|
||||
coin CoinSymbol
|
||||
value float64
|
||||
}
|
||||
|
||||
func parseCoinValueArgs(args []string, validValue func(float64) bool, invalidValueErr error) (coinValueArgs, error) {
|
||||
if len(args) != 2 {
|
||||
return coinValueArgs{}, invalidValueErr
|
||||
}
|
||||
if coin, err := ResolveCoinSymbol(args[0]); err == nil {
|
||||
value, ok := parsePositiveFinite(args[1])
|
||||
if !ok || !validValue(value) {
|
||||
return coinValueArgs{}, invalidValueErr
|
||||
}
|
||||
return coinValueArgs{coin: coin, value: value}, nil
|
||||
}
|
||||
value, ok := parsePositiveFinite(args[0])
|
||||
if !ok || !validValue(value) {
|
||||
if _, secondOK := parsePositiveFinite(args[1]); secondOK {
|
||||
return coinValueArgs{}, ErrUnsupportedCoin
|
||||
}
|
||||
return coinValueArgs{}, invalidValueErr
|
||||
}
|
||||
coin, err := ResolveCoinSymbol(args[1])
|
||||
if err != nil {
|
||||
return coinValueArgs{}, err
|
||||
}
|
||||
return coinValueArgs{coin: coin, value: value}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user