forked from urdt/ussd
		
	Merge branch 'master' into tests-refactor
This commit is contained in:
		
						commit
						d8800a665d
					
				| @ -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,8 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"git.grassecon.net/urdt/ussd/config" | ||||
| 	"git.grassecon.net/urdt/ussd/internal/models" | ||||
| @ -23,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 { | ||||
| @ -164,3 +167,105 @@ 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, | ||||
| 		Description: "Account creation request received successfully", | ||||
| 		Result:      map[string]any{"publicKey": "0x48ADca309b5085852207FAaf2816eD72B52F527C", "trackingId": "28ebe84d-b925-472c-87ae-bbdfa1fb97be"}, | ||||
| 	}, nil | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) { | ||||
| 	balanceResponse := &models.BalanceResponse{ | ||||
| 		Ok: true, | ||||
| 		Result: struct { | ||||
| 			Balance string      `json:"balance"` | ||||
| 			Nonce   json.Number `json:"nonce"` | ||||
| 		}{ | ||||
| 			Balance: "0.003 CELO", | ||||
| 			Nonce:   json.Number("0"), | ||||
| 		}, | ||||
| 	} | ||||
| 	return balanceResponse, nil | ||||
| } | ||||
| 
 | ||||
| func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) { | ||||
| 	return &api.OKResponse{ | ||||
| 		Ok:          true, | ||||
| 		Description: "Account creation succeeded", | ||||
| 		Result: map[string]any{ | ||||
| 			"active": true, | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (tas *TestAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) { | ||||
| 	trackResponse := &models.TrackStatusResponse{ | ||||
| 		Ok: true, | ||||
| 		Result: struct { | ||||
| 			Transaction struct { | ||||
| 				CreatedAt     time.Time   "json:\"createdAt\"" | ||||
| 				Status        string      "json:\"status\"" | ||||
| 				TransferValue json.Number "json:\"transferValue\"" | ||||
| 				TxHash        string      "json:\"txHash\"" | ||||
| 				TxType        string      "json:\"txType\"" | ||||
| 			} | ||||
| 		}{ | ||||
| 			Transaction: models.Transaction{ | ||||
| 				CreatedAt:     time.Now(), | ||||
| 				Status:        "SUCCESS", | ||||
| 				TransferValue: json.Number("0.5"), | ||||
| 				TxHash:        "0x123abc456def", | ||||
| 				TxType:        "transfer", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	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 ( | ||||
| @ -188,33 +190,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) | ||||
| @ -233,6 +208,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 | ||||
| @ -241,6 +219,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) | ||||
| @ -250,11 +229,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 | ||||
| } | ||||
| 
 | ||||
| @ -283,10 +266,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") | ||||
| @ -298,12 +281,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) | ||||
| @ -311,6 +294,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 | ||||
| } | ||||
| 
 | ||||
| @ -639,14 +627,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") | ||||
| @ -657,23 +643,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 | ||||
| } | ||||
| @ -811,15 +799,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 | ||||
| } | ||||
| @ -828,54 +814,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 | ||||
| @ -888,12 +849,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 | ||||
| } | ||||
| 
 | ||||
| @ -936,9 +899,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 | ||||
| } | ||||
| @ -964,7 +934,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 { | ||||
| @ -1048,3 +1020,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 | ||||
| } | ||||
|  | ||||
| @ -219,7 +219,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 { | ||||
| @ -261,10 +261,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) | ||||
| @ -482,37 +482,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) | ||||
| 
 | ||||
| @ -533,26 +502,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) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| @ -981,7 +954,7 @@ func TestVerifyYob(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestVerifyPin(t *testing.T) { | ||||
| func TestVerifyCreatePin(t *testing.T) { | ||||
| 	fm, err := NewFlagManager(flagsPath) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| @ -1030,7 +1003,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) { | ||||
| @ -1038,8 +1011,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) | ||||
| @ -1318,16 +1294,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.", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| @ -1336,6 +1314,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) | ||||
| @ -1465,7 +1444,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) | ||||
| 
 | ||||
| @ -1479,92 +1457,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) | ||||
| 
 | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -1628,80 +1573,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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -1845,42 +1762,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" | ||||
| 
 | ||||
| @ -1928,7 +1809,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) { | ||||
|  | ||||
							
								
								
									
										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) | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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