Merge pull request 'menu-voucherlist' (#101) from menu-voucherlist into master
Reviewed-on: #101
This commit is contained in:
		
						commit
						2dee47404d
					
				@ -61,8 +61,8 @@ func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceIn
 | 
			
		||||
	ussdHandlers = ussdHandlers.WithPersister(ls.Pe)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("set_language", ussdHandlers.SetLanguage)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("verify_create_pin", ussdHandlers.VerifyCreatePin)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
 | 
			
		||||
@ -89,11 +89,15 @@ func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceIn
 | 
			
		||||
	ls.DbRs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("fetch_custodial_balances", ussdHandlers.FetchCustodialBalances)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("set_default_voucher", ussdHandlers.SetDefaultVoucher)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("check_vouchers", ussdHandlers.CheckVouchers)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("view_voucher", ussdHandlers.ViewVoucher)
 | 
			
		||||
	ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher)
 | 
			
		||||
 | 
			
		||||
	return ussdHandlers, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/config"
 | 
			
		||||
@ -24,6 +25,7 @@ type AccountServiceInterface interface {
 | 
			
		||||
	CreateAccount(ctx context.Context) (*api.OKResponse, error)
 | 
			
		||||
	CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error)
 | 
			
		||||
	TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error)
 | 
			
		||||
	FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AccountService struct {
 | 
			
		||||
@ -169,6 +171,23 @@ func (as *AccountService) CreateAccount(ctx context.Context) (*api.OKResponse, e
 | 
			
		||||
	return &okResponse, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FetchVouchers retrieves the token holdings for a given public key from the custodial holdings API endpoint
 | 
			
		||||
// Parameters:
 | 
			
		||||
//   - publicKey: The public key associated with the account.
 | 
			
		||||
func (as *AccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
 | 
			
		||||
	file, err := os.Open("sample_tokens.json")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
	var holdings models.VoucherHoldingResponse
 | 
			
		||||
 | 
			
		||||
	if err := json.NewDecoder(file).Decode(&holdings); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &holdings, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (tas *TestAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
 | 
			
		||||
	return &api.OKResponse{
 | 
			
		||||
		Ok:          true,
 | 
			
		||||
@ -225,3 +244,31 @@ func (tas *TestAccountService) CheckAccountStatus(ctx context.Context, trackingI
 | 
			
		||||
	}
 | 
			
		||||
	return trackResponse, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
 | 
			
		||||
	return &models.VoucherHoldingResponse{
 | 
			
		||||
		Ok: true,
 | 
			
		||||
		Result: struct {
 | 
			
		||||
			Holdings []struct {
 | 
			
		||||
				ContractAddress string `json:"contractAddress"`
 | 
			
		||||
				TokenSymbol     string `json:"tokenSymbol"`
 | 
			
		||||
				TokenDecimals   string `json:"tokenDecimals"`
 | 
			
		||||
				Balance         string `json:"balance"`
 | 
			
		||||
			} `json:"holdings"`
 | 
			
		||||
		}{
 | 
			
		||||
			Holdings: []struct {
 | 
			
		||||
				ContractAddress string `json:"contractAddress"`
 | 
			
		||||
				TokenSymbol     string `json:"tokenSymbol"`
 | 
			
		||||
				TokenDecimals   string `json:"tokenDecimals"`
 | 
			
		||||
				Balance         string `json:"balance"`
 | 
			
		||||
			}{
 | 
			
		||||
				{
 | 
			
		||||
					ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
 | 
			
		||||
					TokenSymbol:     "SRF",
 | 
			
		||||
					TokenDecimals:   "6",
 | 
			
		||||
					Balance:         "2745987",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,8 @@ import (
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/handlers/server"
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/utils"
 | 
			
		||||
	"gopkg.in/leonelquinteros/gotext.v1"
 | 
			
		||||
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/storage"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@ -187,33 +189,6 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte)
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SavePin persists the user's PIN choice into the filesystem
 | 
			
		||||
func (h *Handlers) SavePin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	var res resource.Result
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	sessionId, ok := ctx.Value("SessionId").(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
 | 
			
		||||
	accountPIN := string(input)
 | 
			
		||||
	// Validate that the PIN is a 4-digit number
 | 
			
		||||
	if !isValidPIN(accountPIN) {
 | 
			
		||||
		res.FlagSet = append(res.FlagSet, flag_incorrect_pin)
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
	err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(accountPIN))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	res := resource.Result{}
 | 
			
		||||
	_, ok := ctx.Value("SessionId").(string)
 | 
			
		||||
@ -232,6 +207,9 @@ func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_PIN
 | 
			
		||||
// during the account creation process
 | 
			
		||||
// and during the change PIN process
 | 
			
		||||
func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	var res resource.Result
 | 
			
		||||
	var err error
 | 
			
		||||
@ -240,6 +218,7 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
 | 
			
		||||
 | 
			
		||||
	accountPIN := string(input)
 | 
			
		||||
@ -249,11 +228,15 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt
 | 
			
		||||
		res.FlagSet = append(res.FlagSet, flag_incorrect_pin)
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
 | 
			
		||||
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
	err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN, []byte(accountPIN))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -282,10 +265,10 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VerifyPin checks whether the confirmation PIN is similar to the account PIN
 | 
			
		||||
// If similar, it sets the USERFLAG_PIN_SET flag allowing the user
 | 
			
		||||
// VerifyCreatePin checks whether the confirmation PIN is similar to the temporary PIN
 | 
			
		||||
// If similar, it sets the USERFLAG_PIN_SET flag and writes the account PIN allowing the user
 | 
			
		||||
// to access the main menu
 | 
			
		||||
func (h *Handlers) VerifyPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	var res resource.Result
 | 
			
		||||
 | 
			
		||||
	flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
 | 
			
		||||
@ -297,12 +280,12 @@ func (h *Handlers) VerifyPin(ctx context.Context, sym string, input []byte) (res
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
	AccountPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN)
 | 
			
		||||
	temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if bytes.Equal(input, AccountPin) {
 | 
			
		||||
	if bytes.Equal(input, temporaryPin) {
 | 
			
		||||
		res.FlagSet = []uint32{flag_valid_pin}
 | 
			
		||||
		res.FlagReset = []uint32{flag_pin_mismatch}
 | 
			
		||||
		res.FlagSet = append(res.FlagSet, flag_pin_set)
 | 
			
		||||
@ -310,6 +293,11 @@ func (h *Handlers) VerifyPin(ctx context.Context, sym string, input []byte) (res
 | 
			
		||||
		res.FlagSet = []uint32{flag_pin_mismatch}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(temporaryPin))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -628,14 +616,12 @@ func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []by
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckBalance retrieves the balance from the API using the "PublicKey" and sets
 | 
			
		||||
// CheckBalance retrieves the balance of the active voucher and sets
 | 
			
		||||
// the balance as the result content
 | 
			
		||||
func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	var res resource.Result
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
 | 
			
		||||
 | 
			
		||||
	sessionId, ok := ctx.Value("SessionId").(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
@ -646,23 +632,25 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
 | 
			
		||||
	l.AddDomain("default")
 | 
			
		||||
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
	publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
			
		||||
 | 
			
		||||
	// get the active sym and active balance
 | 
			
		||||
	activeSym, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if db.IsNotFound(err) {
 | 
			
		||||
			balance := "0.00"
 | 
			
		||||
			res.Content = l.Get("Balance: %s\n", balance)
 | 
			
		||||
			return res, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	activeBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	balanceResponse, err := h.accountService.CheckBalance(ctx, string(publicKey))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
	if !balanceResponse.Ok {
 | 
			
		||||
		res.FlagSet = append(res.FlagSet, flag_api_error)
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
	res.FlagReset = append(res.FlagReset, flag_api_error)
 | 
			
		||||
	balance := balanceResponse.Result.Balance
 | 
			
		||||
 | 
			
		||||
	res.Content = l.Get("Balance: %s\n", balance)
 | 
			
		||||
	res.Content = l.Get("Balance: %s\n", fmt.Sprintf("%s %s", activeBal, activeSym))
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
@ -800,15 +788,13 @@ func (h *Handlers) MaxAmount(ctx context.Context, sym string, input []byte) (res
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
	publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
			
		||||
 | 
			
		||||
	balanceResp, err := h.accountService.CheckBalance(ctx, string(publicKey))
 | 
			
		||||
	activeBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, nil
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
	balance := balanceResp.Result.Balance
 | 
			
		||||
 | 
			
		||||
	res.Content = balance
 | 
			
		||||
	res.Content = string(activeBal)
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
@ -817,54 +803,29 @@ func (h *Handlers) MaxAmount(ctx context.Context, sym string, input []byte) (res
 | 
			
		||||
// it is not more than the current balance.
 | 
			
		||||
func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	var res resource.Result
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	sessionId, ok := ctx.Value("SessionId").(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
 | 
			
		||||
	flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
 | 
			
		||||
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
	publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
			
		||||
 | 
			
		||||
	amountStr := string(input)
 | 
			
		||||
	var balanceValue float64
 | 
			
		||||
 | 
			
		||||
	balanceRes, err := h.accountService.CheckBalance(ctx, string(publicKey))
 | 
			
		||||
	balanceStr := balanceRes.Result.Balance
 | 
			
		||||
 | 
			
		||||
	if !balanceRes.Ok {
 | 
			
		||||
		res.FlagSet = append(res.FlagSet, flag_api_error)
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
	// retrieve the active balance
 | 
			
		||||
	activeBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
	res.Content = balanceStr
 | 
			
		||||
	res.FlagReset = append(res.FlagReset, flag_api_error)
 | 
			
		||||
 | 
			
		||||
	// Parse the balance
 | 
			
		||||
	balanceParts := strings.Split(balanceStr, " ")
 | 
			
		||||
	if len(balanceParts) != 2 {
 | 
			
		||||
		return res, fmt.Errorf("unexpected balance format: %s", balanceStr)
 | 
			
		||||
	}
 | 
			
		||||
	balanceValue, err := strconv.ParseFloat(balanceParts[0], 64)
 | 
			
		||||
	balanceValue, err = strconv.ParseFloat(string(activeBal), 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, fmt.Errorf("failed to parse balance: %v", err)
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Extract numeric part from input
 | 
			
		||||
	re := regexp.MustCompile(`^(\d+(\.\d+)?)\s*(?:CELO)?$`)
 | 
			
		||||
	matches := re.FindStringSubmatch(strings.TrimSpace(amountStr))
 | 
			
		||||
	if len(matches) < 2 {
 | 
			
		||||
		res.FlagSet = append(res.FlagSet, flag_invalid_amount)
 | 
			
		||||
		res.Content = amountStr
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inputAmount, err := strconv.ParseFloat(matches[1], 64)
 | 
			
		||||
	// Extract numeric part from the input amount
 | 
			
		||||
	amountStr := strings.TrimSpace(string(input))
 | 
			
		||||
	inputAmount, err := strconv.ParseFloat(amountStr, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res.FlagSet = append(res.FlagSet, flag_invalid_amount)
 | 
			
		||||
		res.Content = amountStr
 | 
			
		||||
@ -877,12 +838,14 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.Content = fmt.Sprintf("%.3f", inputAmount) // Format to 3 decimal places
 | 
			
		||||
	err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(amountStr))
 | 
			
		||||
	// Format the amount with 2 decimal places before saving
 | 
			
		||||
	formattedAmount := fmt.Sprintf("%.2f", inputAmount)
 | 
			
		||||
	err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(formattedAmount))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.Content = fmt.Sprintf("%s", formattedAmount)
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -925,9 +888,16 @@ func (h *Handlers) GetAmount(ctx context.Context, sym string, input []byte) (res
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
 | 
			
		||||
	// retrieve the active symbol
 | 
			
		||||
	activeSym, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	amount, _ := store.ReadEntry(ctx, sessionId, utils.DATA_AMOUNT)
 | 
			
		||||
 | 
			
		||||
	res.Content = string(amount)
 | 
			
		||||
	res.Content = fmt.Sprintf("%s %s", string(amount), string(activeSym))
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
@ -953,7 +923,9 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []
 | 
			
		||||
 | 
			
		||||
	recipient, _ := store.ReadEntry(ctx, sessionId, utils.DATA_RECIPIENT)
 | 
			
		||||
 | 
			
		||||
	res.Content = l.Get("Your request has been sent. %s will receive %s from %s.", string(recipient), string(amount), string(sessionId))
 | 
			
		||||
	activeSym, _ := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM)
 | 
			
		||||
 | 
			
		||||
	res.Content = l.Get("Your request has been sent. %s will receive %s %s from %s.", string(recipient), string(amount), string(activeSym), string(sessionId))
 | 
			
		||||
 | 
			
		||||
	account_authorized_flag, err := h.flagManager.GetFlag("flag_account_authorized")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -1037,3 +1009,279 @@ func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte)
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetDefaultVoucher retrieves the current vouchers
 | 
			
		||||
// and sets the first as the default voucher, if no active voucher is set
 | 
			
		||||
func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	var res resource.Result
 | 
			
		||||
	var err error
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
 | 
			
		||||
	sessionId, ok := ctx.Value("SessionId").(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flag_no_active_voucher, _ := h.flagManager.GetFlag("flag_no_active_voucher")
 | 
			
		||||
 | 
			
		||||
	// check if the user has an active sym
 | 
			
		||||
	_, err = store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if db.IsNotFound(err) {
 | 
			
		||||
			publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return res, nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Fetch vouchers from the API using the public key
 | 
			
		||||
			vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return res, nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Return if there is no voucher
 | 
			
		||||
			if len(vouchersResp.Result.Holdings) == 0 {
 | 
			
		||||
				res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
 | 
			
		||||
				return res, nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Use only the first voucher
 | 
			
		||||
			firstVoucher := vouchersResp.Result.Holdings[0]
 | 
			
		||||
			defaultSym := firstVoucher.TokenSymbol
 | 
			
		||||
			defaultBal := firstVoucher.Balance
 | 
			
		||||
 | 
			
		||||
			// set the active symbol
 | 
			
		||||
			err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(defaultSym))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return res, err
 | 
			
		||||
			}
 | 
			
		||||
			// set the active balance
 | 
			
		||||
			err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL, []byte(defaultBal))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return res, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return res, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.FlagReset = append(res.FlagReset, flag_no_active_voucher)
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckVouchers retrieves the token holdings from the API using the "PublicKey" and stores
 | 
			
		||||
// them to gdbm
 | 
			
		||||
func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	var res resource.Result
 | 
			
		||||
	sessionId, ok := ctx.Value("SessionId").(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
	publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Fetch vouchers from the API using the public key
 | 
			
		||||
	vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// process voucher data
 | 
			
		||||
	voucherSymbolList, voucherBalanceList := ProcessVouchers(vouchersResp.Result.Holdings)
 | 
			
		||||
 | 
			
		||||
	prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers"))
 | 
			
		||||
	err = prefixdb.Put(ctx, []byte("sym"), []byte(voucherSymbolList))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = prefixdb.Put(ctx, []byte("bal"), []byte(voucherBalanceList))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ProcessVouchers formats the holdings into symbol and balance lists.
 | 
			
		||||
func ProcessVouchers(holdings []struct {
 | 
			
		||||
	ContractAddress string `json:"contractAddress"`
 | 
			
		||||
	TokenSymbol     string `json:"tokenSymbol"`
 | 
			
		||||
	TokenDecimals   string `json:"tokenDecimals"`
 | 
			
		||||
	Balance         string `json:"balance"`
 | 
			
		||||
}) (string, string) {
 | 
			
		||||
	var numberedSymbols, numberedBalances []string
 | 
			
		||||
 | 
			
		||||
	for i, voucher := range holdings {
 | 
			
		||||
		numberedSymbols = append(numberedSymbols, fmt.Sprintf("%d:%s", i+1, voucher.TokenSymbol))
 | 
			
		||||
		numberedBalances = append(numberedBalances, fmt.Sprintf("%d:%s", i+1, voucher.Balance))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	voucherSymbolList := strings.Join(numberedSymbols, "\n")
 | 
			
		||||
	voucherBalanceList := strings.Join(numberedBalances, "\n")
 | 
			
		||||
 | 
			
		||||
	return voucherSymbolList, voucherBalanceList
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetVoucherList fetches the list of vouchers and formats them
 | 
			
		||||
func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	var res resource.Result
 | 
			
		||||
 | 
			
		||||
	// Read vouchers from the store
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
	prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers"))
 | 
			
		||||
 | 
			
		||||
	voucherData, err := prefixdb.Get(ctx, []byte("sym"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.Content = string(voucherData)
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ViewVoucher retrieves the token holding and balance from the subprefixDB
 | 
			
		||||
func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	var res resource.Result
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
 | 
			
		||||
	sessionId, ok := ctx.Value("SessionId").(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
 | 
			
		||||
 | 
			
		||||
	inputStr := string(input)
 | 
			
		||||
 | 
			
		||||
	if inputStr == "0" || inputStr == "99" {
 | 
			
		||||
		res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
 | 
			
		||||
		return res, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers"))
 | 
			
		||||
 | 
			
		||||
	// Retrieve the voucher symbol list
 | 
			
		||||
	voucherSymbolList, err := prefixdb.Get(ctx, []byte("sym"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, fmt.Errorf("failed to retrieve voucher symbol list: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Retrieve the voucher balance list
 | 
			
		||||
	voucherBalanceList, err := prefixdb.Get(ctx, []byte("bal"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, fmt.Errorf("failed to retrieve voucher balance list: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// match the voucher symbol and balance with the input
 | 
			
		||||
	matchedSymbol, matchedBalance := MatchVoucher(inputStr, string(voucherSymbolList), string(voucherBalanceList))
 | 
			
		||||
 | 
			
		||||
	// If a match is found, write the temporary sym and balance
 | 
			
		||||
	if matchedSymbol != "" && matchedBalance != "" {
 | 
			
		||||
		err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte(matchedSymbol))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return res, err
 | 
			
		||||
		}
 | 
			
		||||
		err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte(matchedBalance))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return res, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
 | 
			
		||||
		res.Content = fmt.Sprintf("%s\n%s", matchedSymbol, matchedBalance)
 | 
			
		||||
	} else {
 | 
			
		||||
		res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MatchVoucher finds the matching voucher symbol and balance based on the input.
 | 
			
		||||
func MatchVoucher(inputStr string, voucherSymbols, voucherBalances string) (string, string) {
 | 
			
		||||
	// Split the lists into slices for processing
 | 
			
		||||
	symbols := strings.Split(voucherSymbols, "\n")
 | 
			
		||||
	balances := strings.Split(voucherBalances, "\n")
 | 
			
		||||
 | 
			
		||||
	var matchedSymbol, matchedBalance string
 | 
			
		||||
 | 
			
		||||
	for i, symbol := range symbols {
 | 
			
		||||
		symbolParts := strings.SplitN(symbol, ":", 2)
 | 
			
		||||
		if len(symbolParts) != 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		voucherNum := symbolParts[0]
 | 
			
		||||
		voucherSymbol := symbolParts[1]
 | 
			
		||||
 | 
			
		||||
		// Check if input matches either the number or the symbol
 | 
			
		||||
		if inputStr == voucherNum || strings.EqualFold(inputStr, voucherSymbol) {
 | 
			
		||||
			matchedSymbol = voucherSymbol
 | 
			
		||||
			// Ensure there's a corresponding balance
 | 
			
		||||
			if i < len(balances) {
 | 
			
		||||
				matchedBalance = strings.SplitN(balances[i], ":", 2)[1]
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return matchedSymbol, matchedBalance
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetVoucher retrieves the temporary sym and balance,
 | 
			
		||||
// sets them as the active data and
 | 
			
		||||
// clears the temporary data
 | 
			
		||||
func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	var res resource.Result
 | 
			
		||||
	var err error
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
 | 
			
		||||
	sessionId, ok := ctx.Value("SessionId").(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get the current temporary symbol
 | 
			
		||||
	temporarySym, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_SYM)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
	// get the current temporary balance
 | 
			
		||||
	temporaryBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_BAL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// set the active symbol
 | 
			
		||||
	err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(temporarySym))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
	// set the active balance
 | 
			
		||||
	err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL, []byte(temporaryBal))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// reset the temporary symbol
 | 
			
		||||
	err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte(""))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
	// reset the temporary balance
 | 
			
		||||
	err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte(""))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.Content = string(temporarySym)
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -218,7 +218,7 @@ func TestSaveFamilyname(t *testing.T) {
 | 
			
		||||
	mockStore.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSavePin(t *testing.T) {
 | 
			
		||||
func TestSaveTemporaryPin(t *testing.T) {
 | 
			
		||||
	fm, err := NewFlagManager(flagsPath)
 | 
			
		||||
	mockStore := new(mocks.MockUserDataStore)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -260,10 +260,10 @@ func TestSavePin(t *testing.T) {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
			// Set up the expected behavior of the mock
 | 
			
		||||
			mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(tt.input)).Return(nil)
 | 
			
		||||
			mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_PIN, []byte(tt.input)).Return(nil)
 | 
			
		||||
 | 
			
		||||
			// Call the method
 | 
			
		||||
			res, err := h.SavePin(ctx, "save_pin", tt.input)
 | 
			
		||||
			res, err := h.SaveTemporaryPin(ctx, "save_pin", tt.input)
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Error(err)
 | 
			
		||||
@ -481,37 +481,6 @@ func TestCheckIdentifier(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMaxAmount(t *testing.T) {
 | 
			
		||||
	mockStore := new(mocks.MockUserDataStore)
 | 
			
		||||
	mockCreateAccountService := new(mocks.MockAccountService)
 | 
			
		||||
 | 
			
		||||
	// Define test data
 | 
			
		||||
	sessionId := "session123"
 | 
			
		||||
	ctx := context.WithValue(context.Background(), "SessionId", sessionId)
 | 
			
		||||
	publicKey := "0xcasgatweksalw1018221"
 | 
			
		||||
 | 
			
		||||
	expectedBalance := &models.BalanceResponse{
 | 
			
		||||
		Ok: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Set up the expected behavior of the mock
 | 
			
		||||
	mockStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil)
 | 
			
		||||
	mockCreateAccountService.On("CheckBalance", publicKey).Return(expectedBalance, nil)
 | 
			
		||||
 | 
			
		||||
	// Create the Handlers instance with the mock store
 | 
			
		||||
	h := &Handlers{
 | 
			
		||||
		userdataStore:  mockStore,
 | 
			
		||||
		accountService: mockCreateAccountService,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Call the method
 | 
			
		||||
	res, _ := h.MaxAmount(ctx, "max_amount", []byte("check_balance"))
 | 
			
		||||
 | 
			
		||||
	//Assert that the balance that was set as the result content is what was returned by  Check Balance
 | 
			
		||||
	assert.Equal(t, expectedBalance.Result.Balance, res.Content)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetSender(t *testing.T) {
 | 
			
		||||
	mockStore := new(mocks.MockUserDataStore)
 | 
			
		||||
 | 
			
		||||
@ -532,26 +501,30 @@ func TestGetSender(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetAmount(t *testing.T) {
 | 
			
		||||
	mockStore := new(mocks.MockUserDataStore)
 | 
			
		||||
	mockDataStore := new(mocks.MockUserDataStore)
 | 
			
		||||
 | 
			
		||||
	// Define test data
 | 
			
		||||
	sessionId := "session123"
 | 
			
		||||
	ctx := context.WithValue(context.Background(), "SessionId", sessionId)
 | 
			
		||||
	Amount := "0.03CELO"
 | 
			
		||||
	amount := "0.03"
 | 
			
		||||
	activeSym := "SRF"
 | 
			
		||||
 | 
			
		||||
	// Set up the expected behavior of the mock
 | 
			
		||||
	mockStore.On("ReadEntry", ctx, sessionId, utils.DATA_AMOUNT).Return([]byte(Amount), nil)
 | 
			
		||||
	mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_ACTIVE_SYM).Return([]byte(activeSym), nil)
 | 
			
		||||
	mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_AMOUNT).Return([]byte(amount), nil)
 | 
			
		||||
 | 
			
		||||
	// Create the Handlers instance with the mock store
 | 
			
		||||
	h := &Handlers{
 | 
			
		||||
		userdataStore: mockStore,
 | 
			
		||||
		userdataStore: mockDataStore,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Call the method
 | 
			
		||||
	res, _ := h.GetAmount(ctx, "get_amount", []byte("Getting amount..."))
 | 
			
		||||
	res, _ := h.GetAmount(ctx, "get_amount", []byte(""))
 | 
			
		||||
 | 
			
		||||
	formattedAmount := fmt.Sprintf("%s %s", amount, activeSym)
 | 
			
		||||
 | 
			
		||||
	//Assert that the retrieved amount is what was set as the content
 | 
			
		||||
	assert.Equal(t, Amount, res.Content)
 | 
			
		||||
	assert.Equal(t, formattedAmount, res.Content)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -980,7 +953,7 @@ func TestVerifyYob(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVerifyPin(t *testing.T) {
 | 
			
		||||
func TestVerifyCreatePin(t *testing.T) {
 | 
			
		||||
	fm, err := NewFlagManager(flagsPath)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -1029,7 +1002,7 @@ func TestVerifyPin(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	typ := utils.DATA_ACCOUNT_PIN
 | 
			
		||||
	typ := utils.DATA_TEMPORARY_PIN
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
@ -1037,8 +1010,11 @@ func TestVerifyPin(t *testing.T) {
 | 
			
		||||
			// Define expected interactions with the mock
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, sessionId, typ).Return([]byte(firstSetPin), nil)
 | 
			
		||||
 | 
			
		||||
			// Set up the expected behavior of the mock
 | 
			
		||||
			mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(firstSetPin)).Return(nil)
 | 
			
		||||
 | 
			
		||||
			// Call the method under test
 | 
			
		||||
			res, err := h.VerifyPin(ctx, "verify_pin", []byte(tt.input))
 | 
			
		||||
			res, err := h.VerifyCreatePin(ctx, "verify_create_pin", []byte(tt.input))
 | 
			
		||||
 | 
			
		||||
			// Assert that no errors occurred
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
@ -1317,16 +1293,18 @@ func TestInitiateTransaction(t *testing.T) {
 | 
			
		||||
		input          []byte
 | 
			
		||||
		Recipient      []byte
 | 
			
		||||
		Amount         []byte
 | 
			
		||||
		ActiveSym      []byte
 | 
			
		||||
		status         string
 | 
			
		||||
		expectedResult resource.Result
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:      "Test initiate transaction",
 | 
			
		||||
			Amount:    []byte("0.002 CELO"),
 | 
			
		||||
			Amount:    []byte("0.002"),
 | 
			
		||||
			ActiveSym: []byte("SRF"),
 | 
			
		||||
			Recipient: []byte("0x12415ass27192"),
 | 
			
		||||
			expectedResult: resource.Result{
 | 
			
		||||
				FlagReset: []uint32{account_authorized_flag},
 | 
			
		||||
				Content:   "Your request has been sent. 0x12415ass27192 will receive 0.002 CELO from 254712345678.",
 | 
			
		||||
				Content:   "Your request has been sent. 0x12415ass27192 will receive 0.002 SRF from 254712345678.",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@ -1335,6 +1313,7 @@ func TestInitiateTransaction(t *testing.T) {
 | 
			
		||||
			// Define expected interactions with the mock
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_AMOUNT).Return(tt.Amount, nil)
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_RECIPIENT).Return(tt.Recipient, nil)
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_ACTIVE_SYM).Return(tt.ActiveSym, nil)
 | 
			
		||||
 | 
			
		||||
			// Call the method under test
 | 
			
		||||
			res, _ := h.InitiateTransaction(ctx, "transaction_reset_amount", tt.input)
 | 
			
		||||
@ -1464,7 +1443,6 @@ func TestValidateAmount(t *testing.T) {
 | 
			
		||||
		t.Logf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	flag_invalid_amount, _ := fm.parser.GetFlag("flag_invalid_amount")
 | 
			
		||||
	flag_api_error, _ := fm.GetFlag("flag_api_call_error")
 | 
			
		||||
	mockDataStore := new(mocks.MockUserDataStore)
 | 
			
		||||
	mockCreateAccountService := new(mocks.MockAccountService)
 | 
			
		||||
 | 
			
		||||
@ -1478,92 +1456,59 @@ func TestValidateAmount(t *testing.T) {
 | 
			
		||||
		flagManager:    fm.parser,
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name            string
 | 
			
		||||
		input           []byte
 | 
			
		||||
		publicKey       []byte
 | 
			
		||||
		balanceResponse *models.BalanceResponse
 | 
			
		||||
		expectedResult  resource.Result
 | 
			
		||||
		name           string
 | 
			
		||||
		input          []byte
 | 
			
		||||
		activeBal      []byte
 | 
			
		||||
		balance        string
 | 
			
		||||
		expectedResult resource.Result
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:  "Test with valid amount",
 | 
			
		||||
			input: []byte("0.001"),
 | 
			
		||||
			balanceResponse: &models.BalanceResponse{
 | 
			
		||||
				Ok: true,
 | 
			
		||||
				Result: struct {
 | 
			
		||||
					Balance string      `json:"balance"`
 | 
			
		||||
					Nonce   json.Number `json:"nonce"`
 | 
			
		||||
				}{
 | 
			
		||||
					Balance: "0.003 CELO",
 | 
			
		||||
					Nonce:   json.Number("0"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			publicKey: []byte("0xrqeqrequuq"),
 | 
			
		||||
			name:      "Test with valid amount",
 | 
			
		||||
			input:     []byte("4.10"),
 | 
			
		||||
			activeBal: []byte("5"),
 | 
			
		||||
			expectedResult: resource.Result{
 | 
			
		||||
				Content:   "0.001",
 | 
			
		||||
				FlagReset: []uint32{flag_api_error},
 | 
			
		||||
				Content: "4.10",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:  "Test with amount larger than balance",
 | 
			
		||||
			input: []byte("0.02"),
 | 
			
		||||
			balanceResponse: &models.BalanceResponse{
 | 
			
		||||
				Ok: true,
 | 
			
		||||
				Result: struct {
 | 
			
		||||
					Balance string      `json:"balance"`
 | 
			
		||||
					Nonce   json.Number `json:"nonce"`
 | 
			
		||||
				}{
 | 
			
		||||
					Balance: "0.003 CELO",
 | 
			
		||||
					Nonce:   json.Number("0"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			publicKey: []byte("0xrqeqrequuq"),
 | 
			
		||||
			name:      "Test with amount larger than active balance",
 | 
			
		||||
			input:     []byte("5.02"),
 | 
			
		||||
			activeBal: []byte("5"),
 | 
			
		||||
			expectedResult: resource.Result{
 | 
			
		||||
				FlagSet:   []uint32{flag_invalid_amount},
 | 
			
		||||
				FlagReset: []uint32{flag_api_error},
 | 
			
		||||
				Content:   "0.02",
 | 
			
		||||
				FlagSet: []uint32{flag_invalid_amount},
 | 
			
		||||
				Content: "5.02",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:  "Test with invalid amount",
 | 
			
		||||
			input: []byte("0.02ms"),
 | 
			
		||||
			balanceResponse: &models.BalanceResponse{
 | 
			
		||||
				Ok: true,
 | 
			
		||||
				Result: struct {
 | 
			
		||||
					Balance string      `json:"balance"`
 | 
			
		||||
					Nonce   json.Number `json:"nonce"`
 | 
			
		||||
				}{
 | 
			
		||||
					Balance: "0.003 CELO",
 | 
			
		||||
					Nonce:   json.Number("0"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			publicKey: []byte("0xrqeqrequuq"),
 | 
			
		||||
			name:    "Test with invalid amount format",
 | 
			
		||||
			input:   []byte("0.02ms"),
 | 
			
		||||
			activeBal: []byte("5"),
 | 
			
		||||
			expectedResult: resource.Result{
 | 
			
		||||
				FlagSet:   []uint32{flag_invalid_amount},
 | 
			
		||||
				FlagReset: []uint32{flag_api_error},
 | 
			
		||||
				Content:   "0.02ms",
 | 
			
		||||
				FlagSet: []uint32{flag_invalid_amount},
 | 
			
		||||
				Content: "0.02ms",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			// Mock behavior for active balance retrieval
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_ACTIVE_BAL).Return(tt.activeBal, nil)
 | 
			
		||||
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return(tt.publicKey, nil)
 | 
			
		||||
			mockCreateAccountService.On("CheckBalance", string(tt.publicKey)).Return(tt.balanceResponse, nil)
 | 
			
		||||
			// Mock behavior for storing the amount (if valid)
 | 
			
		||||
			mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_AMOUNT, tt.input).Return(nil).Maybe()
 | 
			
		||||
 | 
			
		||||
			// Call the method under test
 | 
			
		||||
			res, _ := h.ValidateAmount(ctx, "test_validate_amount", tt.input)
 | 
			
		||||
 | 
			
		||||
			// Assert that no errors occurred
 | 
			
		||||
			// Assert no errors occurred
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
			//Assert that the account created flag has been set to the result
 | 
			
		||||
			assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
 | 
			
		||||
			// Assert the result matches the expected result
 | 
			
		||||
			assert.Equal(t, tt.expectedResult, res, "Expected result should match actual result")
 | 
			
		||||
 | 
			
		||||
			// Assert that expectations were met
 | 
			
		||||
			// Assert all expectations were met
 | 
			
		||||
			mockDataStore.AssertExpectations(t)
 | 
			
		||||
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1627,80 +1572,52 @@ func TestValidateRecipient(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCheckBalance(t *testing.T) {
 | 
			
		||||
	sessionId := "session123"
 | 
			
		||||
	publicKey := "0X13242618721"
 | 
			
		||||
	fm, _ := NewFlagManager(flagsPath)
 | 
			
		||||
	flag_api_error, _ := fm.GetFlag("flag_api_call_error")
 | 
			
		||||
 | 
			
		||||
	ctx := context.WithValue(context.Background(), "SessionId", sessionId)
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name           string
 | 
			
		||||
		balanceResonse *models.BalanceResponse
 | 
			
		||||
		sessionId      string
 | 
			
		||||
		publicKey      string
 | 
			
		||||
		activeSym      string
 | 
			
		||||
		activeBal      string
 | 
			
		||||
		expectedResult resource.Result
 | 
			
		||||
		expectError    bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "Test when checking a balance is not a success",
 | 
			
		||||
			balanceResonse: &models.BalanceResponse{
 | 
			
		||||
				Ok: false,
 | 
			
		||||
				Result: struct {
 | 
			
		||||
					Balance string      `json:"balance"`
 | 
			
		||||
					Nonce   json.Number `json:"nonce"`
 | 
			
		||||
				}{
 | 
			
		||||
					Balance: "0.003 CELO",
 | 
			
		||||
					Nonce:   json.Number("0"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectedResult: resource.Result{
 | 
			
		||||
				FlagSet: []uint32{flag_api_error},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Test when checking a balance is a success",
 | 
			
		||||
			balanceResonse: &models.BalanceResponse{
 | 
			
		||||
				Ok: true,
 | 
			
		||||
				Result: struct {
 | 
			
		||||
					Balance string      `json:"balance"`
 | 
			
		||||
					Nonce   json.Number `json:"nonce"`
 | 
			
		||||
				}{
 | 
			
		||||
					Balance: "0.003 CELO",
 | 
			
		||||
					Nonce:   json.Number("0"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectedResult: resource.Result{
 | 
			
		||||
				Content:   "Balance: 0.003 CELO\n",
 | 
			
		||||
				FlagReset: []uint32{flag_api_error},
 | 
			
		||||
			},
 | 
			
		||||
			name:           "User with active sym",
 | 
			
		||||
			sessionId:      "session456",
 | 
			
		||||
			publicKey:      "0X98765432109",
 | 
			
		||||
			activeSym:      "ETH",
 | 
			
		||||
			activeBal:      "1.5",
 | 
			
		||||
			expectedResult: resource.Result{Content: "Balance: 1.5 ETH\n"},
 | 
			
		||||
			expectError:    false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
			mockDataStore := new(mocks.MockUserDataStore)
 | 
			
		||||
			mockCreateAccountService := new(mocks.MockAccountService)
 | 
			
		||||
			mockState := state.NewState(16)
 | 
			
		||||
			mockAccountService := new(mocks.MockAccountService)
 | 
			
		||||
			ctx := context.WithValue(context.Background(), "SessionId", tt.sessionId)
 | 
			
		||||
 | 
			
		||||
			// Create the Handlers instance with the mock store
 | 
			
		||||
			h := &Handlers{
 | 
			
		||||
				userdataStore:  mockDataStore,
 | 
			
		||||
				flagManager:    fm.parser,
 | 
			
		||||
				st:             mockState,
 | 
			
		||||
				accountService: mockCreateAccountService,
 | 
			
		||||
				accountService: mockAccountService,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Set up the expected behavior of the mock
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil)
 | 
			
		||||
			mockCreateAccountService.On("CheckBalance", string(publicKey)).Return(tt.balanceResonse, nil)
 | 
			
		||||
			// Mock for user with active sym
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, tt.sessionId, utils.DATA_ACTIVE_SYM).Return([]byte(tt.activeSym), nil)
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, tt.sessionId, utils.DATA_ACTIVE_BAL).Return([]byte(tt.activeBal), nil)
 | 
			
		||||
 | 
			
		||||
			// Call the method
 | 
			
		||||
			res, _ := h.CheckBalance(ctx, "check_balance", []byte(""))
 | 
			
		||||
			res, err := h.CheckBalance(ctx, "check_balance", []byte(""))
 | 
			
		||||
 | 
			
		||||
			if tt.expectError {
 | 
			
		||||
				assert.Error(t, err)
 | 
			
		||||
			} else {
 | 
			
		||||
				assert.NoError(t, err)
 | 
			
		||||
				assert.Equal(t, tt.expectedResult, res, "Result should match expected output")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Assert that expectations were met
 | 
			
		||||
			mockDataStore.AssertExpectations(t)
 | 
			
		||||
 | 
			
		||||
			//Assert that the result set to content is what was expected
 | 
			
		||||
			assert.Equal(t, res, tt.expectedResult, "Result should contain flags set according to user input")
 | 
			
		||||
			mockAccountService.AssertExpectations(t)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1844,42 +1761,6 @@ func TestVerifyNewPin(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSaveTemporaryPIn(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	fm, err := NewFlagManager(flagsPath)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Logf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create a new instance of UserDataStore
 | 
			
		||||
	mockStore := new(mocks.MockUserDataStore)
 | 
			
		||||
 | 
			
		||||
	// Define test data
 | 
			
		||||
	sessionId := "session123"
 | 
			
		||||
	PIN := "1234"
 | 
			
		||||
	ctx := context.WithValue(context.Background(), "SessionId", sessionId)
 | 
			
		||||
 | 
			
		||||
	// Set up the expected behavior of the mock
 | 
			
		||||
	mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_PIN, []byte(PIN)).Return(nil)
 | 
			
		||||
 | 
			
		||||
	// Create the Handlers instance with the mock store
 | 
			
		||||
	h := &Handlers{
 | 
			
		||||
		userdataStore: mockStore,
 | 
			
		||||
		flagManager:   fm.parser,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Call the method
 | 
			
		||||
	res, err := h.SaveTemporaryPin(ctx, "save_temporary_pin", []byte(PIN))
 | 
			
		||||
 | 
			
		||||
	// Assert results
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, resource.Result{}, res)
 | 
			
		||||
 | 
			
		||||
	// Assert all expectations were met
 | 
			
		||||
	mockStore.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestConfirmPin(t *testing.T) {
 | 
			
		||||
	sessionId := "session123"
 | 
			
		||||
 | 
			
		||||
@ -1927,7 +1808,40 @@ func TestConfirmPin(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetVoucher(t *testing.T) {
 | 
			
		||||
	mockDataStore := new(mocks.MockUserDataStore)
 | 
			
		||||
 | 
			
		||||
	sessionId := "session123"
 | 
			
		||||
	ctx := context.WithValue(context.Background(), "SessionId", sessionId)
 | 
			
		||||
 | 
			
		||||
	temporarySym := []byte("tempSym")
 | 
			
		||||
	temporaryBal := []byte("tempBal")
 | 
			
		||||
 | 
			
		||||
	// Set expectations for the mock data store
 | 
			
		||||
	mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM).Return(temporarySym, nil)
 | 
			
		||||
	mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL).Return(temporaryBal, nil)
 | 
			
		||||
	mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_SYM, temporarySym).Return(nil)
 | 
			
		||||
	mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_BAL, temporaryBal).Return(nil)
 | 
			
		||||
	mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte("")).Return(nil)
 | 
			
		||||
	mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte("")).Return(nil)
 | 
			
		||||
 | 
			
		||||
	h := &Handlers{
 | 
			
		||||
		userdataStore: mockDataStore,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Call the method under test
 | 
			
		||||
	res, err := h.SetVoucher(ctx, "someSym", []byte{})
 | 
			
		||||
 | 
			
		||||
	// Assert that no errors occurred
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	// Assert that the result content is correct
 | 
			
		||||
	assert.Equal(t, string(temporarySym), res.Content)
 | 
			
		||||
 | 
			
		||||
	// Assert that expectations were met
 | 
			
		||||
	mockDataStore.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFetchCustodialBalances(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
@ -32,3 +32,9 @@ func (m *MockAccountService) TrackAccountStatus(ctx context.Context,publicKey st
 | 
			
		||||
	args := m.Called(publicKey)
 | 
			
		||||
	return args.Get(0).(*api.OKResponse), args.Error(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func (m *MockAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
 | 
			
		||||
	args := m.Called(publicKey)
 | 
			
		||||
	return args.Get(0).(*models.VoucherHoldingResponse), args.Error(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										18
									
								
								internal/models/tokenresponse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								internal/models/tokenresponse.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type ApiResponse struct {
 | 
			
		||||
	OK          bool   `json:"ok"`
 | 
			
		||||
	Description string `json:"description"`
 | 
			
		||||
	Result      Result `json:"result"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Result struct {
 | 
			
		||||
	Holdings []Holding `json:"holdings"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Holding struct {
 | 
			
		||||
	ContractAddress string `json:"contractAddress"`
 | 
			
		||||
	TokenSymbol     string `json:"tokenSymbol"`
 | 
			
		||||
	TokenDecimals   string `json:"tokenDecimals"`
 | 
			
		||||
	Balance         string `json:"balance"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								internal/models/vouchersresponse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								internal/models/vouchersresponse.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
// VoucherHoldingResponse represents a single voucher holding
 | 
			
		||||
type VoucherHoldingResponse struct {
 | 
			
		||||
	Ok          bool   `json:"ok"`
 | 
			
		||||
	Description string `json:"description"`
 | 
			
		||||
	Result      struct {
 | 
			
		||||
		Holdings []struct {
 | 
			
		||||
			ContractAddress string `json:"contractAddress"`
 | 
			
		||||
			TokenSymbol     string `json:"tokenSymbol"`
 | 
			
		||||
			TokenDecimals   string `json:"tokenDecimals"`
 | 
			
		||||
			Balance         string `json:"balance"`
 | 
			
		||||
		} `json:"holdings"`
 | 
			
		||||
	} `json:"result"`
 | 
			
		||||
}
 | 
			
		||||
@ -12,32 +12,32 @@ const (
 | 
			
		||||
 | 
			
		||||
type SubPrefixDb struct {
 | 
			
		||||
	store db.Db
 | 
			
		||||
	pfx []byte
 | 
			
		||||
	pfx   []byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewSubPrefixDb(store db.Db, pfx []byte) *SubPrefixDb {
 | 
			
		||||
	return &SubPrefixDb{
 | 
			
		||||
		store: store,
 | 
			
		||||
		pfx: pfx,
 | 
			
		||||
		pfx:   pfx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func(s *SubPrefixDb) toKey(k []byte) []byte {
 | 
			
		||||
        return append(s.pfx, k...)
 | 
			
		||||
func (s *SubPrefixDb) toKey(k []byte) []byte {
 | 
			
		||||
	return append(s.pfx, k...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func(s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
 | 
			
		||||
        s.store.SetPrefix(DATATYPE_USERSUB)
 | 
			
		||||
func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
 | 
			
		||||
	s.store.SetPrefix(DATATYPE_USERSUB)
 | 
			
		||||
	key = s.toKey(key)
 | 
			
		||||
        v, err := s.store.Get(ctx, key)
 | 
			
		||||
        if err != nil {
 | 
			
		||||
                return nil, err
 | 
			
		||||
        }
 | 
			
		||||
        return v, nil
 | 
			
		||||
	v, err := s.store.Get(ctx, key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return v, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func(s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
 | 
			
		||||
        s.store.SetPrefix(DATATYPE_USERSUB)
 | 
			
		||||
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
 | 
			
		||||
	s.store.SetPrefix(DATATYPE_USERSUB)
 | 
			
		||||
	key = s.toKey(key)
 | 
			
		||||
        return s.store.Put(ctx, key, val)
 | 
			
		||||
	return s.store.Put(ctx, key, val)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,11 @@ const (
 | 
			
		||||
	DATA_RECIPIENT
 | 
			
		||||
	DATA_AMOUNT
 | 
			
		||||
	DATA_TEMPORARY_PIN
 | 
			
		||||
	DATA_VOUCHER_LIST
 | 
			
		||||
	DATA_TEMPORARY_SYM
 | 
			
		||||
	DATA_ACTIVE_SYM
 | 
			
		||||
	DATA_TEMPORARY_BAL
 | 
			
		||||
	DATA_ACTIVE_BAL
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func typToBytes(typ DataTyp) []byte {
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@
 | 
			
		||||
            "steps": [
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "3",
 | 
			
		||||
@ -33,7 +33,7 @@
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "0",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
@ -42,7 +42,7 @@
 | 
			
		||||
            "steps": [
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "3",
 | 
			
		||||
@ -70,7 +70,7 @@
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "0",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
@ -79,7 +79,7 @@
 | 
			
		||||
            "steps": [
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "3",
 | 
			
		||||
@ -116,7 +116,7 @@
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "0",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
@ -125,7 +125,7 @@
 | 
			
		||||
            "steps": [
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "3",
 | 
			
		||||
@ -162,7 +162,7 @@
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "0",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
@ -171,7 +171,7 @@
 | 
			
		||||
            "steps": [
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "3",
 | 
			
		||||
@ -203,7 +203,7 @@
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "0",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
@ -212,7 +212,7 @@
 | 
			
		||||
            "steps": [
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "3",
 | 
			
		||||
@ -244,7 +244,7 @@
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "0",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                }
 | 
			
		||||
               
 | 
			
		||||
            ]
 | 
			
		||||
@ -254,7 +254,7 @@
 | 
			
		||||
            "steps": [
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "3",
 | 
			
		||||
@ -286,7 +286,7 @@
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "0",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
@ -295,7 +295,7 @@
 | 
			
		||||
            "steps": [
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "3",
 | 
			
		||||
@ -327,7 +327,7 @@
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "0",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
@ -336,7 +336,7 @@
 | 
			
		||||
            "steps": [
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "3",
 | 
			
		||||
@ -368,7 +368,7 @@
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "0",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
@ -377,7 +377,7 @@
 | 
			
		||||
            "steps": [
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "3",
 | 
			
		||||
@ -409,7 +409,7 @@
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "0",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
@ -418,7 +418,7 @@
 | 
			
		||||
            "steps": [
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "3",
 | 
			
		||||
@ -446,7 +446,7 @@
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "input": "0",
 | 
			
		||||
                    "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -43,6 +43,39 @@ func extractPublicKey(response []byte) string {
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Extracts the balance value from the engine response.
 | 
			
		||||
func extractBalance(response []byte) string {
 | 
			
		||||
	// Regex to match "Balance: <amount> <symbol>" followed by a newline
 | 
			
		||||
	re := regexp.MustCompile(`(?m)^Balance:\s+(\d+(\.\d+)?)\s+([A-Z]+)`)
 | 
			
		||||
	match := re.FindSubmatch(response)
 | 
			
		||||
	if match != nil {
 | 
			
		||||
		return string(match[1]) + " " + string(match[3]) // "<amount> <symbol>"
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Extracts the Maximum amount value from the engine response.
 | 
			
		||||
func extractMaxAmount(response []byte) string {
 | 
			
		||||
	// Regex to match "Maximum amount: <amount>" followed by a newline
 | 
			
		||||
	re := regexp.MustCompile(`(?m)^Maximum amount:\s+(\d+(\.\d+)?)`)
 | 
			
		||||
	match := re.FindSubmatch(response)
 | 
			
		||||
	if match != nil {
 | 
			
		||||
		return string(match[1]) // "<amount>"
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Extracts the send amount value from the engine response.
 | 
			
		||||
func extractSendAmount(response []byte) string {
 | 
			
		||||
	// Regex to match the pattern "will receive X.XX SYM from"
 | 
			
		||||
	re := regexp.MustCompile(`will receive (\d+\.\d{2}\s+[A-Z]+) from`)
 | 
			
		||||
	match := re.FindSubmatch(response)
 | 
			
		||||
	if match != nil {
 | 
			
		||||
		return string(match[1]) // Returns "X.XX SYM"
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMain(m *testing.M) {
 | 
			
		||||
	sessionID = GenerateSessionId()
 | 
			
		||||
	defer func() {
 | 
			
		||||
@ -154,6 +187,12 @@ func TestMainMenuHelp(t *testing.T) {
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				b := w.Bytes()
 | 
			
		||||
				balance := extractBalance(b)
 | 
			
		||||
 | 
			
		||||
				expectedContent := []byte(step.ExpectedContent)
 | 
			
		||||
				expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
 | 
			
		||||
 | 
			
		||||
				step.ExpectedContent = string(expectedContent)
 | 
			
		||||
				match, err := step.MatchesExpectedContent(b)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					t.Fatalf("Error compiling regex for step '%s': %v", step.Input, err)
 | 
			
		||||
@ -189,6 +228,12 @@ func TestMainMenuQuit(t *testing.T) {
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				b := w.Bytes()
 | 
			
		||||
				balance := extractBalance(b)
 | 
			
		||||
 | 
			
		||||
				expectedContent := []byte(step.ExpectedContent)
 | 
			
		||||
				expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
 | 
			
		||||
 | 
			
		||||
				step.ExpectedContent = string(expectedContent)
 | 
			
		||||
				match, err := step.MatchesExpectedContent(b)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					t.Fatalf("Error compiling regex for step '%s': %v", step.Input, err)
 | 
			
		||||
@ -225,8 +270,13 @@ func TestMyAccount_MyAddress(t *testing.T) {
 | 
			
		||||
				}
 | 
			
		||||
				b := w.Bytes()
 | 
			
		||||
 | 
			
		||||
				balance := extractBalance(b)
 | 
			
		||||
				publicKey := extractPublicKey(b)
 | 
			
		||||
				expectedContent := bytes.Replace([]byte(step.ExpectedContent), []byte("{public_key}"), []byte(publicKey), -1)
 | 
			
		||||
 | 
			
		||||
				expectedContent := []byte(step.ExpectedContent)
 | 
			
		||||
				expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
 | 
			
		||||
				expectedContent = bytes.Replace(expectedContent, []byte("{public_key}"), []byte(publicKey), -1)
 | 
			
		||||
 | 
			
		||||
				step.ExpectedContent = string(expectedContent)
 | 
			
		||||
				match, err := step.MatchesExpectedContent(b)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
@ -240,6 +290,52 @@ func TestMyAccount_MyAddress(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMainMenuSend(t *testing.T) {
 | 
			
		||||
	en, fn, _ := testutil.TestEngine(sessionID)
 | 
			
		||||
	defer fn()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	sessions := testData
 | 
			
		||||
	for _, session := range sessions {
 | 
			
		||||
		groups := driver.FilterGroupsByName(session.Groups, "send_with_invalid_inputs")
 | 
			
		||||
		for _, group := range groups {
 | 
			
		||||
			for _, step := range group.Steps {
 | 
			
		||||
				cont, err := en.Exec(ctx, []byte(step.Input))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				if !cont {
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				w := bytes.NewBuffer(nil)
 | 
			
		||||
				if _, err := en.Flush(ctx, w); err != nil {
 | 
			
		||||
					t.Fatalf("Test case '%s' failed during Flush: %v", group.Name, err)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				b := w.Bytes()
 | 
			
		||||
				balance := extractBalance(b)
 | 
			
		||||
				max_amount := extractMaxAmount(b)
 | 
			
		||||
				send_amount := extractSendAmount(b)
 | 
			
		||||
 | 
			
		||||
				expectedContent := []byte(step.ExpectedContent)
 | 
			
		||||
				expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
 | 
			
		||||
				expectedContent = bytes.Replace(expectedContent, []byte("{max_amount}"), []byte(max_amount), -1)
 | 
			
		||||
				expectedContent = bytes.Replace(expectedContent, []byte("{send_amount}"), []byte(send_amount), -1)
 | 
			
		||||
				expectedContent = bytes.Replace(expectedContent, []byte("{session_id}"), []byte(sessionID), -1)
 | 
			
		||||
 | 
			
		||||
				step.ExpectedContent = string(expectedContent)
 | 
			
		||||
				match, err := step.MatchesExpectedContent(b)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					t.Fatalf("Error compiling regex for step '%s': %v", step.Input, err)
 | 
			
		||||
				}
 | 
			
		||||
				if !match {
 | 
			
		||||
					t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGroups(t *testing.T) {
 | 
			
		||||
	groups, err := driver.LoadTestGroups(groupTestFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -265,6 +361,13 @@ func TestGroups(t *testing.T) {
 | 
			
		||||
				t.Errorf("Test case '%s' failed during Flush: %v", tt.Name, err)
 | 
			
		||||
			}
 | 
			
		||||
			b := w.Bytes()
 | 
			
		||||
			balance := extractBalance(b)
 | 
			
		||||
 | 
			
		||||
			expectedContent := []byte(tt.ExpectedContent)
 | 
			
		||||
			expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
 | 
			
		||||
 | 
			
		||||
			tt.ExpectedContent = string(expectedContent)
 | 
			
		||||
 | 
			
		||||
			match, err := tt.MatchesExpectedContent(b)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("Error compiling regex for step '%s': %v", tt.Input, err)
 | 
			
		||||
@ -272,7 +375,6 @@ func TestGroups(t *testing.T) {
 | 
			
		||||
			if !match {
 | 
			
		||||
				t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", tt.ExpectedContent, b)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -57,7 +57,7 @@
 | 
			
		||||
                "steps": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "",
 | 
			
		||||
                        "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                        "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "1",
 | 
			
		||||
@ -73,19 +73,19 @@
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "065656",
 | 
			
		||||
                        "expectedContent": "Maximum amount: 0.003 CELO\nEnter amount:\n0:Back"
 | 
			
		||||
                        "expectedContent": "{max_amount}\nEnter amount:\n0:Back"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "0.1",
 | 
			
		||||
                        "expectedContent": "Amount 0.1 is invalid, please try again:\n1:retry\n9:Quit"
 | 
			
		||||
                        "input": "10000000",
 | 
			
		||||
                        "expectedContent": "Amount 10000000 is invalid, please try again:\n1:retry\n9:Quit"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "1",
 | 
			
		||||
                        "expectedContent": "Maximum amount: 0.003 CELO\nEnter amount:\n0:Back"
 | 
			
		||||
                        "expectedContent": "{max_amount}\nEnter amount:\n0:Back"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "0.001",
 | 
			
		||||
                        "expectedContent": "065656 will receive 0.001 from {public_key}\nPlease enter your PIN to confirm:\n0:Back\n9:Quit"
 | 
			
		||||
                        "input": "1.00",
 | 
			
		||||
                        "expectedContent": "065656 will receive {send_amount} from {session_id}\nPlease enter your PIN to confirm:\n0:Back\n9:Quit"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "1222",
 | 
			
		||||
@ -93,11 +93,11 @@
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "1",
 | 
			
		||||
                        "expectedContent": "065656 will receive 0.001 from {public_key}\nPlease enter your PIN to confirm:\n0:Back\n9:Quit"
 | 
			
		||||
                        "expectedContent": "065656 will receive {send_amount} from {session_id}\nPlease enter your PIN to confirm:\n0:Back\n9:Quit"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "1234",
 | 
			
		||||
                        "expectedContent": "Your request has been sent. 065656 will receive 0.001 from {public_key}."
 | 
			
		||||
                        "expectedContent": "Your request has been sent. 065656 will receive {send_amount} from {session_id}."
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
@ -106,7 +106,7 @@
 | 
			
		||||
                "steps": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "",
 | 
			
		||||
                        "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                        "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "4",
 | 
			
		||||
@ -119,7 +119,7 @@
 | 
			
		||||
                "steps": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "",
 | 
			
		||||
                        "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                        "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "9",
 | 
			
		||||
@ -132,7 +132,7 @@
 | 
			
		||||
                "steps": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "",
 | 
			
		||||
                        "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                        "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "input": "3",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										44
									
								
								sample_tokens.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								sample_tokens.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
			
		||||
{
 | 
			
		||||
    "ok": true,
 | 
			
		||||
    "description": "Token holdings with current balances",
 | 
			
		||||
    "result": {
 | 
			
		||||
      "holdings": [
 | 
			
		||||
        {
 | 
			
		||||
          "contractAddress": "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
 | 
			
		||||
          "tokenSymbol": "FSPTST",
 | 
			
		||||
          "tokenDecimals": "6",
 | 
			
		||||
          "balance": "8869964242"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "contractAddress": "0x724F2910D790B54A39a7638282a45B1D83564fFA",
 | 
			
		||||
          "tokenSymbol": "GEO",
 | 
			
		||||
          "tokenDecimals": "6",
 | 
			
		||||
          "balance": "9884"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "contractAddress": "0x2105a206B7bec31E2F90acF7385cc8F7F5f9D273",
 | 
			
		||||
          "tokenSymbol": "MFNK",
 | 
			
		||||
          "tokenDecimals": "6",
 | 
			
		||||
          "balance": "19788697"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "contractAddress": "0x63DE2Ac8D1008351Cc69Fb8aCb94Ba47728a7E83",
 | 
			
		||||
          "tokenSymbol": "MILO",
 | 
			
		||||
          "tokenDecimals": "6",
 | 
			
		||||
          "balance": "75"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "contractAddress": "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
 | 
			
		||||
          "tokenSymbol": "SOHAIL",
 | 
			
		||||
          "tokenDecimals": "6",
 | 
			
		||||
          "balance": "27874115"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "contractAddress": "0x45d747172e77d55575c197CbA9451bC2CD8F4958",
 | 
			
		||||
          "tokenSymbol": "SRF",
 | 
			
		||||
          "tokenDecimals": "6",
 | 
			
		||||
          "balance": "2745987"
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
RELOAD verify_pin
 | 
			
		||||
RELOAD verify_create_pin
 | 
			
		||||
CATCH create_pin_mismatch flag_pin_mismatch 1
 | 
			
		||||
LOAD quit 0
 | 
			
		||||
HALT
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
LOAD reset_transaction_amount 0
 | 
			
		||||
LOAD max_amount 10
 | 
			
		||||
RELOAD max_amount
 | 
			
		||||
MAP max_amount
 | 
			
		||||
MOUT back 0
 | 
			
		||||
HALT
 | 
			
		||||
@ -10,5 +11,5 @@ CATCH invalid_amount flag_invalid_amount 1
 | 
			
		||||
INCMP _ 0
 | 
			
		||||
LOAD get_recipient 12
 | 
			
		||||
LOAD get_sender 64
 | 
			
		||||
LOAD get_amount 12
 | 
			
		||||
LOAD get_amount 32
 | 
			
		||||
INCMP transaction_pin *
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
LOAD save_pin 0
 | 
			
		||||
LOAD save_temporary_pin 6
 | 
			
		||||
HALT
 | 
			
		||||
LOAD verify_pin 8
 | 
			
		||||
LOAD verify_create_pin 8
 | 
			
		||||
INCMP account_creation *
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,8 @@ LOAD create_account 0
 | 
			
		||||
CATCH account_creation_failed flag_account_creation_failed 1
 | 
			
		||||
MOUT exit 0
 | 
			
		||||
HALT
 | 
			
		||||
LOAD save_pin 0
 | 
			
		||||
RELOAD save_pin
 | 
			
		||||
LOAD save_temporary_pin 6
 | 
			
		||||
RELOAD save_temporary_pin
 | 
			
		||||
CATCH . flag_incorrect_pin 1
 | 
			
		||||
INCMP quit 0
 | 
			
		||||
INCMP confirm_create_pin *
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
msgid "Your account balance is %s"
 | 
			
		||||
msgstr "Salio lako ni %s"
 | 
			
		||||
 | 
			
		||||
msgid "Your request has been sent. %s will receive %s from %s."
 | 
			
		||||
msgstr "Ombi lako limetumwa. %s atapokea %s kutoka kwa %s."
 | 
			
		||||
msgid "Your request has been sent. %s will receive %s %s from %s."
 | 
			
		||||
msgstr "Ombi lako limetumwa. %s atapokea %s %s kutoka kwa %s."
 | 
			
		||||
 | 
			
		||||
msgid "Thank you for using Sarafu. Goodbye!"
 | 
			
		||||
msgstr "Asante kwa kutumia huduma ya Sarafu. Kwaheri!"
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,9 @@
 | 
			
		||||
LOAD set_default_voucher 8
 | 
			
		||||
RELOAD set_default_voucher
 | 
			
		||||
LOAD check_balance 64
 | 
			
		||||
RELOAD check_balance
 | 
			
		||||
LOAD check_vouchers 10
 | 
			
		||||
RELOAD check_vouchers
 | 
			
		||||
CATCH api_failure  flag_api_call_error  1
 | 
			
		||||
MAP check_balance
 | 
			
		||||
MOUT send 1
 | 
			
		||||
@ -9,7 +13,7 @@ MOUT help 4
 | 
			
		||||
MOUT quit 9
 | 
			
		||||
HALT
 | 
			
		||||
INCMP send 1
 | 
			
		||||
INCMP quit 2
 | 
			
		||||
INCMP my_vouchers 2
 | 
			
		||||
INCMP my_account 3
 | 
			
		||||
INCMP help 4
 | 
			
		||||
INCMP quit 9
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								services/registration/my_vouchers
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/my_vouchers
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
My vouchers
 | 
			
		||||
							
								
								
									
										8
									
								
								services/registration/my_vouchers.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								services/registration/my_vouchers.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
LOAD reset_account_authorized 16
 | 
			
		||||
RELOAD reset_account_authorized
 | 
			
		||||
MOUT select_voucher 1
 | 
			
		||||
MOUT voucher_details 2
 | 
			
		||||
MOUT back 0
 | 
			
		||||
HALT
 | 
			
		||||
INCMP _ 0
 | 
			
		||||
INCMP select_voucher 1
 | 
			
		||||
							
								
								
									
										1
									
								
								services/registration/no_voucher
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/no_voucher
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
You need a voucher to send
 | 
			
		||||
							
								
								
									
										5
									
								
								services/registration/no_voucher.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								services/registration/no_voucher.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
MOUT back 0
 | 
			
		||||
MOUT quit 9
 | 
			
		||||
HALT
 | 
			
		||||
INCMP ^ 0
 | 
			
		||||
INCMP quit 9
 | 
			
		||||
							
								
								
									
										1
									
								
								services/registration/no_voucher_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/no_voucher_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
Unahitaji sarafu kutuma
 | 
			
		||||
@ -14,4 +14,6 @@ flag,flag_valid_pin,20,this is set when the given PIN is valid
 | 
			
		||||
flag,flag_allow_update,21,this is set to allow a user to update their profile data
 | 
			
		||||
flag,flag_single_edit,22,this is set to allow a user to edit a single profile item such as year of birth
 | 
			
		||||
flag,flag_incorrect_date_format,23,this is set when the given year of birth is invalid
 | 
			
		||||
flag,flag_api_call_error,25,this is set when communication  to an external service fails
 | 
			
		||||
flag,flag_incorrect_voucher,24,this is set when the selected voucher is invalid
 | 
			
		||||
flag,flag_api_call_error,25,this is set when communication to an external service fails
 | 
			
		||||
flag,flag_no_active_voucher,26,this is set when a user does not have an active voucher
 | 
			
		||||
 | 
			
		||||
		
		
			
  | 
							
								
								
									
										2
									
								
								services/registration/select_voucher
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								services/registration/select_voucher
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
Select number or symbol from your vouchers:
 | 
			
		||||
{{.get_vouchers}}
 | 
			
		||||
							
								
								
									
										15
									
								
								services/registration/select_voucher.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								services/registration/select_voucher.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
LOAD get_vouchers 0
 | 
			
		||||
MAP get_vouchers
 | 
			
		||||
MOUT back 0
 | 
			
		||||
MOUT quit 99
 | 
			
		||||
MNEXT next 11
 | 
			
		||||
MPREV prev 22
 | 
			
		||||
HALT
 | 
			
		||||
LOAD view_voucher 80
 | 
			
		||||
RELOAD view_voucher
 | 
			
		||||
CATCH . flag_incorrect_voucher 1
 | 
			
		||||
INCMP _ 0
 | 
			
		||||
INCMP quit 99
 | 
			
		||||
INCMP > 11
 | 
			
		||||
INCMP < 22
 | 
			
		||||
INCMP view_voucher *
 | 
			
		||||
							
								
								
									
										1
									
								
								services/registration/select_voucher_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/select_voucher_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
Select voucher
 | 
			
		||||
							
								
								
									
										2
									
								
								services/registration/select_voucher_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								services/registration/select_voucher_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
Chagua nambari au ishara kutoka kwa salio zako:
 | 
			
		||||
{{.get_vouchers}}
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
LOAD transaction_reset 0
 | 
			
		||||
CATCH no_voucher flag_no_active_voucher 1
 | 
			
		||||
MOUT back 0
 | 
			
		||||
HALT
 | 
			
		||||
LOAD validate_recipient 20
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								services/registration/view_voucher
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								services/registration/view_voucher
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
Enter PIN to confirm selection:
 | 
			
		||||
{{.view_voucher}}
 | 
			
		||||
							
								
								
									
										10
									
								
								services/registration/view_voucher.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								services/registration/view_voucher.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
MAP view_voucher
 | 
			
		||||
MOUT back 0
 | 
			
		||||
MOUT quit 9
 | 
			
		||||
LOAD authorize_account 6
 | 
			
		||||
HALT
 | 
			
		||||
RELOAD authorize_account
 | 
			
		||||
CATCH incorrect_pin flag_incorrect_pin 1
 | 
			
		||||
INCMP _ 0
 | 
			
		||||
INCMP quit 9
 | 
			
		||||
INCMP voucher_set *
 | 
			
		||||
							
								
								
									
										2
									
								
								services/registration/view_voucher_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								services/registration/view_voucher_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
Weka PIN ili kuthibitisha chaguo:
 | 
			
		||||
{{.view_voucher}}
 | 
			
		||||
							
								
								
									
										1
									
								
								services/registration/voucher_details_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/voucher_details_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
Voucher details
 | 
			
		||||
							
								
								
									
										1
									
								
								services/registration/voucher_set
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/voucher_set
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
Success! {{.set_voucher}} is now your active voucher.
 | 
			
		||||
							
								
								
									
										10
									
								
								services/registration/voucher_set.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								services/registration/voucher_set.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
LOAD reset_incorrect 6
 | 
			
		||||
CATCH incorrect_pin flag_incorrect_pin 1
 | 
			
		||||
CATCH _ flag_account_authorized 0
 | 
			
		||||
LOAD set_voucher 12
 | 
			
		||||
MAP set_voucher
 | 
			
		||||
MOUT back 0
 | 
			
		||||
MOUT quit 9
 | 
			
		||||
HALT
 | 
			
		||||
INCMP ^ 0
 | 
			
		||||
INCMP quit 9
 | 
			
		||||
							
								
								
									
										1
									
								
								services/registration/voucher_set_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/voucher_set_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
Hongera! {{.set_voucher}} ni Sarafu inayotumika sasa.
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user