From 098ea9343966dc19d2f76368a197badf3187ec34 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 1 Jul 2025 01:44:38 +0300 Subject: [PATCH 01/26] move create account functionality to the registration.go file --- handlers/application/menuhandler.go | 78 ------------------------ handlers/application/registration.go | 89 ++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 78 deletions(-) create mode 100644 handlers/application/registration.go diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 77abdd2..0107278 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -20,7 +20,6 @@ import ( "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/state" - "git.grassecon.net/grassrootseconomics/common/hex" "git.grassecon.net/grassrootseconomics/common/identity" commonlang "git.grassecon.net/grassrootseconomics/common/lang" "git.grassecon.net/grassrootseconomics/common/person" @@ -197,83 +196,6 @@ func (h *MenuHandlers) SetLanguage(ctx context.Context, sym string, input []byte return res, nil } -// handles the account creation when no existing account is present for the session and stores associated data in the user data store. -func (h *MenuHandlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error { - flag_account_created, _ := h.flagManager.GetFlag("flag_account_created") - flag_account_creation_failed, _ := h.flagManager.GetFlag("flag_account_creation_failed") - - r, err := h.accountService.CreateAccount(ctx) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_account_creation_failed) - logg.ErrorCtxf(ctx, "failed to create an account", "error", err) - return nil - } - res.FlagReset = append(res.FlagReset, flag_account_creation_failed) - - trackingId := r.TrackingId - publicKey := r.PublicKey - - data := map[storedb.DataTyp]string{ - storedb.DATA_TRACKING_ID: trackingId, - storedb.DATA_PUBLIC_KEY: publicKey, - storedb.DATA_ACCOUNT_ALIAS: "", - } - store := h.userdataStore - logdb := h.logDb - for key, value := range data { - err = store.WriteEntry(ctx, sessionId, key, []byte(value)) - if err != nil { - return err - } - err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write log entry", "key", key, "value", value) - } - } - publicKeyNormalized, err := hex.NormalizeHex(publicKey) - if err != nil { - return err - } - err = store.WriteEntry(ctx, publicKeyNormalized, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId)) - if err != nil { - return err - } - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write log entry", "key", storedb.DATA_PUBLIC_KEY_REVERSE, "value", sessionId) - } - - res.FlagSet = append(res.FlagSet, flag_account_created) - return nil -} - -// CreateAccount checks if any account exists on the JSON data file, and if not, -// creates an account on the API, -// sets the default values and flags. -func (h *MenuHandlers) CreateAccount(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") - } - store := h.userdataStore - _, err = store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - if db.IsNotFound(err) { - logg.InfoCtxf(ctx, "Creating an account because it doesn't exist") - err = h.createAccountNoExist(ctx, sessionId, &res) - if err != nil { - logg.ErrorCtxf(ctx, "failed on createAccountNoExist", "error", err) - return res, err - } - } - } - - return res, nil -} - func (h *MenuHandlers) CheckAccountCreated(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result flag_language_set, _ := h.flagManager.GetFlag("flag_language_set") diff --git a/handlers/application/registration.go b/handlers/application/registration.go new file mode 100644 index 0000000..076790d --- /dev/null +++ b/handlers/application/registration.go @@ -0,0 +1,89 @@ +package application + +import ( + "context" + "fmt" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/resource" + + "git.grassecon.net/grassrootseconomics/common/hex" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" +) + +// handles the account creation when no existing account is present for the session and stores associated data in the user data store. +func (h *MenuHandlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error { + flag_account_created, _ := h.flagManager.GetFlag("flag_account_created") + flag_account_creation_failed, _ := h.flagManager.GetFlag("flag_account_creation_failed") + + r, err := h.accountService.CreateAccount(ctx) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_account_creation_failed) + logg.ErrorCtxf(ctx, "failed to create an account", "error", err) + return nil + } + res.FlagReset = append(res.FlagReset, flag_account_creation_failed) + + trackingId := r.TrackingId + publicKey := r.PublicKey + + data := map[storedb.DataTyp]string{ + storedb.DATA_TRACKING_ID: trackingId, + storedb.DATA_PUBLIC_KEY: publicKey, + storedb.DATA_ACCOUNT_ALIAS: "", + } + store := h.userdataStore + logdb := h.logDb + for key, value := range data { + err = store.WriteEntry(ctx, sessionId, key, []byte(value)) + if err != nil { + return err + } + err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write log entry", "key", key, "value", value) + } + } + publicKeyNormalized, err := hex.NormalizeHex(publicKey) + if err != nil { + return err + } + err = store.WriteEntry(ctx, publicKeyNormalized, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId)) + if err != nil { + return err + } + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write log entry", "key", storedb.DATA_PUBLIC_KEY_REVERSE, "value", sessionId) + } + + res.FlagSet = append(res.FlagSet, flag_account_created) + return nil +} + +// CreateAccount checks if any account exists on the JSON data file, and if not, +// creates an account on the API, +// sets the default values and flags. +func (h *MenuHandlers) CreateAccount(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") + } + store := h.userdataStore + _, err = store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + if db.IsNotFound(err) { + logg.InfoCtxf(ctx, "Creating an account because it doesn't exist") + err = h.createAccountNoExist(ctx, sessionId, &res) + if err != nil { + logg.ErrorCtxf(ctx, "failed on createAccountNoExist", "error", err) + return res, err + } + } + } + + return res, nil +} -- 2.45.2 From a9e0aebddef188c3a7a55241de5498c32a03e257 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 1 Jul 2025 01:50:20 +0300 Subject: [PATCH 02/26] move PIN functionality to pin.go --- handlers/application/menuhandler.go | 359 --------------------------- handlers/application/pin.go | 372 ++++++++++++++++++++++++++++ 2 files changed, 372 insertions(+), 359 deletions(-) create mode 100644 handlers/application/pin.go diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 0107278..13f3ee1 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -266,99 +266,6 @@ func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input return res, nil } -// ResetIncorrectPin resets the incorrect pin flag after a new PIN attempt. -func (h *MenuHandlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - store := h.userdataStore - - flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") - flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked") - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - res.FlagReset = append(res.FlagReset, flag_incorrect_pin) - - currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) - if err != nil { - if !db.IsNotFound(err) { - return res, err - } - } - pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) - remainingPINAttempts := pin.AllowedPINAttempts - uint8(pinAttemptsValue) - if remainingPINAttempts == 0 { - res.FlagSet = append(res.FlagSet, flag_account_blocked) - return res, nil - } - if remainingPINAttempts < pin.AllowedPINAttempts { - res.Content = strconv.Itoa(int(remainingPINAttempts)) - } - - return res, nil -} - -// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_VALUE, -// during the account creation process -// and during the change PIN process. -func (h *MenuHandlers) SaveTemporaryPin(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_pin, _ := h.flagManager.GetFlag("flag_invalid_pin") - - if string(input) == "0" { - return res, nil - } - - accountPIN := string(input) - - // Validate that the PIN has a valid format. - if !pin.IsValidPIN(accountPIN) { - res.FlagSet = append(res.FlagSet, flag_invalid_pin) - return res, nil - } - res.FlagReset = append(res.FlagReset, flag_invalid_pin) - - // Hash the PIN - hashedPIN, err := pin.HashPIN(string(accountPIN)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to hash the PIN", "error", err) - return res, err - } - - store := h.userdataStore - logdb := h.logDb - - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryAccountPIN entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err) - return res, err - } - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write temporaryAccountPIN log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err) - } - - return res, nil -} - -// ResetInvalidPIN resets the invalid PIN flag -func (h *MenuHandlers) ResetInvalidPIN(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin") - res.FlagReset = append(res.FlagReset, flag_invalid_pin) - return res, nil -} - // ResetApiCallFailure resets the api call failure flag func (h *MenuHandlers) ResetApiCallFailure(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -367,199 +274,6 @@ func (h *MenuHandlers) ResetApiCallFailure(ctx context.Context, sym string, inpu return res, nil } -// ConfirmPinChange validates user's new PIN. If input matches the temporary PIN, saves it as the new account PIN. -func (h *MenuHandlers) ConfirmPinChange(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") - } - flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") - flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset") - - if string(input) == "0" { - res.FlagReset = append(res.FlagReset, flag_pin_mismatch) - return res, nil - } - - store := h.userdataStore - logdb := h.logDb - hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err) - return res, err - } - if len(hashedTemporaryPin) == 0 { - logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - - if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) { - res.FlagReset = append(res.FlagReset, flag_pin_mismatch) - } else { - res.FlagSet = append(res.FlagSet, flag_pin_mismatch) - return res, nil - } - - // save the hashed PIN as the new account PIN - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", storedb.DATA_ACCOUNT_PIN, "hashedPIN value", hashedTemporaryPin, "error", err) - return res, err - } - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write AccountPIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err) - } - - // set the DATA_SELF_PIN_RESET as 0 - err = store.WriteEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET, []byte("0")) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write DATA_SELF_PIN_RESET entry with", "key", storedb.DATA_SELF_PIN_RESET, "self PIN reset value", "0", "error", err) - return res, err - } - res.FlagReset = append(res.FlagReset, flag_account_pin_reset) - - return res, nil -} - -// ValidateBlockedNumber performs validation of phone numbers during the Reset other's PIN. -// It checks phone number format and verifies registration status. -// If valid, it writes the number under DATA_BLOCKED_NUMBER on the admin account -func (h *MenuHandlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - var err error - - flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number") - store := h.userdataStore - logdb := h.logDb - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - if string(input) == "0" { - res.FlagReset = append(res.FlagReset, flag_unregistered_number) - return res, nil - } - - blockedNumber := string(input) - formattedNumber, err := phone.FormatPhoneNumber(blockedNumber) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_unregistered_number) - logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", blockedNumber, "error", err) - return res, nil - } - - _, err = store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY) - if err != nil { - if db.IsNotFound(err) { - logg.InfoCtxf(ctx, "Invalid or unregistered number") - res.FlagSet = append(res.FlagSet, flag_unregistered_number) - return res, nil - } else { - logg.ErrorCtxf(ctx, "Error on ValidateBlockedNumber", "error", err) - return res, err - } - } - - err = store.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber)) - if err != nil { - return res, nil - } - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write blocked number log entry", "key", storedb.DATA_BLOCKED_NUMBER, "value", formattedNumber, "error", err) - } - - return res, nil -} - -// ResetOthersPin handles the PIN reset process for other users' accounts by: -// 1. Retrieving the blocked phone number from the session -// 2. Writing the DATA_SELF_PIN_RESET on the blocked phone number -// 3. Resetting the DATA_INCORRECT_PIN_ATTEMPTS to 0 for the blocked phone number -func (h *MenuHandlers) ResetOthersPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - - store := h.userdataStore - smsservice := h.smsService - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - blockedPhonenumber, err := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read blockedPhonenumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err) - return res, err - } - - // set the DATA_SELF_PIN_RESET for the account - err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_SELF_PIN_RESET, []byte("1")) - if err != nil { - return res, nil - } - - err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) - if err != nil { - logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err) - return res, err - } - blockedPhoneStr := string(blockedPhonenumber) - //Trigger an SMS to inform a user that the blocked account has been reset - if phone.IsValidPhoneNumber(blockedPhoneStr) { - err = smsservice.SendPINResetSMS(ctx, sessionId, blockedPhoneStr) - if err != nil { - logg.DebugCtxf(ctx, "Failed to send PIN reset SMS", "error", err) - return res, nil - } - } - return res, nil -} - -// incrementIncorrectPINAttempts keeps track of the number of incorrect PIN attempts -func (h *MenuHandlers) incrementIncorrectPINAttempts(ctx context.Context, sessionId string) error { - var pinAttemptsCount uint8 - store := h.userdataStore - - currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) - if err != nil { - if db.IsNotFound(err) { - //First time Wrong PIN attempt: initialize with a count of 1 - pinAttemptsCount = 1 - err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount)))) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", currentWrongPinAttempts, "error", err) - return err - } - return nil - } - } - pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) - pinAttemptsCount = uint8(pinAttemptsValue) + 1 - - err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount)))) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", pinAttemptsCount, "error", err) - return err - } - return nil -} - -// resetIncorrectPINAttempts resets the number of incorrect PIN attempts after a correct PIN entry -func (h *MenuHandlers) resetIncorrectPINAttempts(ctx context.Context, sessionId string) error { - store := h.userdataStore - err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) - if err != nil { - logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err) - return err - } - return nil -} - // ResetUnregisteredNumber clears the unregistered number flag in the system, // indicating that a number's registration status should no longer be marked as unregistered. func (h *MenuHandlers) ResetUnregisteredNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) { @@ -569,63 +283,6 @@ func (h *MenuHandlers) ResetUnregisteredNumber(ctx context.Context, sym string, return res, nil } -// 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 *MenuHandlers) VerifyCreatePin(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") - } - - flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin") - flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") - flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set") - - if string(input) == "0" { - res.FlagReset = append(res.FlagReset, flag_pin_mismatch) - return res, nil - } - - store := h.userdataStore - logdb := h.logDb - - hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err) - return res, err - } - if len(hashedTemporaryPin) == 0 { - logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - - if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) { - res.FlagSet = append(res.FlagSet, flag_valid_pin) - res.FlagSet = append(res.FlagSet, flag_pin_set) - res.FlagReset = append(res.FlagReset, flag_pin_mismatch) - } else { - res.FlagSet = append(res.FlagSet, flag_pin_mismatch) - return res, nil - } - - // save the hashed PIN as the new account PIN - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err) - return res, err - } - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write DATA_ACCOUNT_PIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err) - } - - return res, nil -} - // SaveFirstname updates the first name in the gdbm with the provided input. func (h *MenuHandlers) SaveFirstname(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -1821,22 +1478,6 @@ func (h *MenuHandlers) GetRecipient(ctx context.Context, sym string, input []byt return res, nil } -// RetrieveBlockedNumber gets the current number during the pin reset for other's is in progress. -func (h *MenuHandlers) RetrieveBlockedNumber(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 - blockedNumber, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) - - res.Content = string(blockedNumber) - - return res, nil -} - // GetSender returns the sessionId (phoneNumber). func (h *MenuHandlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result diff --git a/handlers/application/pin.go b/handlers/application/pin.go new file mode 100644 index 0000000..d3999f3 --- /dev/null +++ b/handlers/application/pin.go @@ -0,0 +1,372 @@ +package application + +import ( + "context" + "fmt" + "strconv" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/common/phone" + "git.grassecon.net/grassrootseconomics/common/pin" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" +) + +// ResetIncorrectPin resets the incorrect pin flag after a new PIN attempt. +func (h *MenuHandlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + store := h.userdataStore + + flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") + flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked") + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + res.FlagReset = append(res.FlagReset, flag_incorrect_pin) + + currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) + if err != nil { + if !db.IsNotFound(err) { + return res, err + } + } + pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) + remainingPINAttempts := pin.AllowedPINAttempts - uint8(pinAttemptsValue) + if remainingPINAttempts == 0 { + res.FlagSet = append(res.FlagSet, flag_account_blocked) + return res, nil + } + if remainingPINAttempts < pin.AllowedPINAttempts { + res.Content = strconv.Itoa(int(remainingPINAttempts)) + } + + return res, nil +} + +// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_VALUE, +// during the account creation process +// and during the change PIN process. +func (h *MenuHandlers) SaveTemporaryPin(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_pin, _ := h.flagManager.GetFlag("flag_invalid_pin") + + if string(input) == "0" { + return res, nil + } + + accountPIN := string(input) + + // Validate that the PIN has a valid format. + if !pin.IsValidPIN(accountPIN) { + res.FlagSet = append(res.FlagSet, flag_invalid_pin) + return res, nil + } + res.FlagReset = append(res.FlagReset, flag_invalid_pin) + + // Hash the PIN + hashedPIN, err := pin.HashPIN(string(accountPIN)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to hash the PIN", "error", err) + return res, err + } + + store := h.userdataStore + logdb := h.logDb + + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporaryAccountPIN entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err) + return res, err + } + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write temporaryAccountPIN log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err) + } + + return res, nil +} + +// ResetInvalidPIN resets the invalid PIN flag +func (h *MenuHandlers) ResetInvalidPIN(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin") + res.FlagReset = append(res.FlagReset, flag_invalid_pin) + return res, nil +} + +// ConfirmPinChange validates user's new PIN. If input matches the temporary PIN, saves it as the new account PIN. +func (h *MenuHandlers) ConfirmPinChange(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") + } + flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") + flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset") + + if string(input) == "0" { + res.FlagReset = append(res.FlagReset, flag_pin_mismatch) + return res, nil + } + + store := h.userdataStore + logdb := h.logDb + hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err) + return res, err + } + if len(hashedTemporaryPin) == 0 { + logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE) + return res, fmt.Errorf("Data error encountered") + } + + if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) { + res.FlagReset = append(res.FlagReset, flag_pin_mismatch) + } else { + res.FlagSet = append(res.FlagSet, flag_pin_mismatch) + return res, nil + } + + // save the hashed PIN as the new account PIN + err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", storedb.DATA_ACCOUNT_PIN, "hashedPIN value", hashedTemporaryPin, "error", err) + return res, err + } + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write AccountPIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err) + } + + // set the DATA_SELF_PIN_RESET as 0 + err = store.WriteEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET, []byte("0")) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write DATA_SELF_PIN_RESET entry with", "key", storedb.DATA_SELF_PIN_RESET, "self PIN reset value", "0", "error", err) + return res, err + } + res.FlagReset = append(res.FlagReset, flag_account_pin_reset) + + return res, nil +} + +// ValidateBlockedNumber performs validation of phone numbers during the Reset other's PIN. +// It checks phone number format and verifies registration status. +// If valid, it writes the number under DATA_BLOCKED_NUMBER on the admin account +func (h *MenuHandlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + var err error + + flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number") + store := h.userdataStore + logdb := h.logDb + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + if string(input) == "0" { + res.FlagReset = append(res.FlagReset, flag_unregistered_number) + return res, nil + } + + blockedNumber := string(input) + formattedNumber, err := phone.FormatPhoneNumber(blockedNumber) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_unregistered_number) + logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", blockedNumber, "error", err) + return res, nil + } + + _, err = store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY) + if err != nil { + if db.IsNotFound(err) { + logg.InfoCtxf(ctx, "Invalid or unregistered number") + res.FlagSet = append(res.FlagSet, flag_unregistered_number) + return res, nil + } else { + logg.ErrorCtxf(ctx, "Error on ValidateBlockedNumber", "error", err) + return res, err + } + } + + err = store.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber)) + if err != nil { + return res, nil + } + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write blocked number log entry", "key", storedb.DATA_BLOCKED_NUMBER, "value", formattedNumber, "error", err) + } + + return res, nil +} + +// ResetOthersPin handles the PIN reset process for other users' accounts by: +// 1. Retrieving the blocked phone number from the session +// 2. Writing the DATA_SELF_PIN_RESET on the blocked phone number +// 3. Resetting the DATA_INCORRECT_PIN_ATTEMPTS to 0 for the blocked phone number +func (h *MenuHandlers) ResetOthersPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + store := h.userdataStore + smsservice := h.smsService + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + blockedPhonenumber, err := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read blockedPhonenumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err) + return res, err + } + + // set the DATA_SELF_PIN_RESET for the account + err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_SELF_PIN_RESET, []byte("1")) + if err != nil { + return res, nil + } + + err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) + if err != nil { + logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err) + return res, err + } + blockedPhoneStr := string(blockedPhonenumber) + //Trigger an SMS to inform a user that the blocked account has been reset + if phone.IsValidPhoneNumber(blockedPhoneStr) { + err = smsservice.SendPINResetSMS(ctx, sessionId, blockedPhoneStr) + if err != nil { + logg.DebugCtxf(ctx, "Failed to send PIN reset SMS", "error", err) + return res, nil + } + } + return res, nil +} + +// incrementIncorrectPINAttempts keeps track of the number of incorrect PIN attempts +func (h *MenuHandlers) incrementIncorrectPINAttempts(ctx context.Context, sessionId string) error { + var pinAttemptsCount uint8 + store := h.userdataStore + + currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) + if err != nil { + if db.IsNotFound(err) { + //First time Wrong PIN attempt: initialize with a count of 1 + pinAttemptsCount = 1 + err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount)))) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", currentWrongPinAttempts, "error", err) + return err + } + return nil + } + } + pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) + pinAttemptsCount = uint8(pinAttemptsValue) + 1 + + err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount)))) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", pinAttemptsCount, "error", err) + return err + } + return nil +} + +// resetIncorrectPINAttempts resets the number of incorrect PIN attempts after a correct PIN entry +func (h *MenuHandlers) resetIncorrectPINAttempts(ctx context.Context, sessionId string) error { + store := h.userdataStore + err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) + if err != nil { + logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err) + return err + } + return nil +} + +// 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 *MenuHandlers) VerifyCreatePin(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") + } + + flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin") + flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") + flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set") + + if string(input) == "0" { + res.FlagReset = append(res.FlagReset, flag_pin_mismatch) + return res, nil + } + + store := h.userdataStore + logdb := h.logDb + + hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err) + return res, err + } + if len(hashedTemporaryPin) == 0 { + logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE) + return res, fmt.Errorf("Data error encountered") + } + + if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) { + res.FlagSet = append(res.FlagSet, flag_valid_pin) + res.FlagSet = append(res.FlagSet, flag_pin_set) + res.FlagReset = append(res.FlagReset, flag_pin_mismatch) + } else { + res.FlagSet = append(res.FlagSet, flag_pin_mismatch) + return res, nil + } + + // save the hashed PIN as the new account PIN + err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err) + return res, err + } + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write DATA_ACCOUNT_PIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err) + } + + return res, nil +} + +// RetrieveBlockedNumber gets the current number during the pin reset for other's is in progress. +func (h *MenuHandlers) RetrieveBlockedNumber(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 + blockedNumber, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) + + res.Content = string(blockedNumber) + + return res, nil +} -- 2.45.2 From 9722df1149c13b1f2258238c2379fe815be47b71 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 1 Jul 2025 01:53:03 +0300 Subject: [PATCH 03/26] added poolswap.go for pool swap functionality --- handlers/application/menuhandler.go | 413 --------------------------- handlers/application/poolswap.go | 427 ++++++++++++++++++++++++++++ 2 files changed, 427 insertions(+), 413 deletions(-) create mode 100644 handlers/application/poolswap.go diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 13f3ee1..56a80fa 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -2309,416 +2309,3 @@ func (h *MenuHandlers) ClearTemporaryValue(ctx context.Context, sym string, inpu } return res, nil } - -// GetPools fetches a list of 5 top pools -func (h *MenuHandlers) GetPools(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") - } - userStore := h.userdataStore - - flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") - - // call the api to get a list of top 5 pools sorted by swaps - topPools, err := h.accountService.FetchTopPools(ctx) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) - return res, err - } - - // Return if there are no pools - if len(topPools) == 0 { - return res, nil - } - - data := store.ProcessPools(topPools) - - // Store all Pool data - dataMap := map[storedb.DataTyp]string{ - storedb.DATA_POOL_NAMES: data.PoolNames, - storedb.DATA_POOL_SYMBOLS: data.PoolSymbols, - storedb.DATA_POOL_ADDRESSES: data.PoolContractAdrresses, - } - - // Write data entries - for key, value := range dataMap { - if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { - logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) - continue - } - } - - res.Content = h.ReplaceSeparatorFunc(data.PoolSymbols) - - return res, nil -} - -// LoadSwapFromList returns a list of possible vouchers to swap to -func (h *MenuHandlers) LoadSwapToList(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") - } - - userStore := h.userdataStore - - // get the active address and symbol - activeAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "error", err) - return res, err - } - activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) - return res, err - } - - code := codeFromCtx(ctx) - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - - flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") - flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") - - inputStr := string(input) - if inputStr == "0" { - return res, nil - } - - // Get active pool address and symbol or fall back to default - var activePoolAddress []byte - activePoolAddress, err = userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS) - if err != nil { - if db.IsNotFound(err) { - defaultPoolAddress := config.DefaultPoolAddress() - // store the default as the active pool address - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS, []byte(defaultPoolAddress)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write default PoolContractAdrress", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "value", defaultPoolAddress, "error", err) - return res, err - } - activePoolAddress = []byte(defaultPoolAddress) - } else { - logg.ErrorCtxf(ctx, "failed to read active PoolContractAdrress", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "error", err) - return res, err - } - } - - var activePoolSymbol []byte - activePoolSymbol, err = userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM) - if err != nil { - if db.IsNotFound(err) { - defaultPoolSym := config.DefaultPoolName() - // store the default as the active pool symbol - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM, []byte(defaultPoolSym)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write default Pool Symbol", "key", storedb.DATA_ACTIVE_POOL_SYM, "value", defaultPoolSym, "error", err) - return res, err - } - activePoolSymbol = []byte(defaultPoolSym) - } else { - logg.ErrorCtxf(ctx, "failed to read active Pool symbol", "key", storedb.DATA_ACTIVE_POOL_SYM, "error", err) - return res, err - } - } - - // call the api using the ActivePoolAddress and ActiveVoucherAddress to check if it is part of the pool - r, err := h.accountService.CheckTokenInPool(ctx, string(activePoolAddress), string(activeAddress)) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed on CheckTokenInPool", "error", err) - return res, err - } - - logg.InfoCtxf(ctx, "CheckTokenInPool", "response", r, "active_pool_address", string(activePoolAddress), "active_symbol_address", string(activeAddress)) - - if !r.CanSwapFrom { - res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) - res.Content = l.Get( - "%s is not in %s. Please update your voucher and try again.", - activeSym, - activePoolSymbol, - ) - return res, nil - } - - res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) - - // call the api using the activePoolAddress to get a list of SwapToSymbolsData - swapToList, err := h.accountService.GetPoolSwappableVouchers(ctx, string(activePoolAddress)) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) - return res, err - } - - logg.InfoCtxf(ctx, "GetPoolSwappableVouchers", "swapToList", swapToList) - - // Return if there are no vouchers - if len(swapToList) == 0 { - return res, nil - } - - data := store.ProcessVouchers(swapToList) - - logg.InfoCtxf(ctx, "ProcessVouchers", "data", data) - - // Store all swap_to tokens data - dataMap := map[storedb.DataTyp]string{ - storedb.DATA_POOL_TO_SYMBOLS: data.Symbols, - storedb.DATA_POOL_TO_BALANCES: data.Balances, - storedb.DATA_POOL_TO_DECIMALS: data.Decimals, - storedb.DATA_POOL_TO_ADDRESSES: data.Addresses, - } - - for key, value := range dataMap { - if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { - logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) - continue - } - } - - res.Content = h.ReplaceSeparatorFunc(data.Symbols) - - return res, nil -} - -// SwapMaxLimit returns the max FROM token -// check if max/tokenDecimals > 0.1 for UX purposes and to prevent swapping of dust values -func (h *MenuHandlers) SwapMaxLimit(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") - } - - flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") - flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") - flag_low_swap_amount, _ := h.flagManager.GetFlag("flag_low_swap_amount") - - res.FlagReset = append(res.FlagReset, flag_incorrect_voucher, flag_low_swap_amount) - - inputStr := string(input) - if inputStr == "0" { - return res, nil - } - - userStore := h.userdataStore - metadata, err := store.GetSwapToVoucherData(ctx, userStore, sessionId, inputStr) - if err != nil { - return res, fmt.Errorf("failed to retrieve swap to voucher data: %v", err) - } - if metadata == nil { - res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) - return res, nil - } - - logg.InfoCtxf(ctx, "Metadata from GetSwapToVoucherData:", "metadata", metadata) - - // Store the active swap_to data - if err := store.UpdateSwapToVoucherData(ctx, userStore, sessionId, metadata); err != nil { - logg.ErrorCtxf(ctx, "failed on UpdateSwapToVoucherData", "error", err) - return res, err - } - - swapData, err := store.ReadSwapData(ctx, userStore, sessionId) - if err != nil { - return res, err - } - - // call the api using the ActivePoolAddress, ActiveSwapFromAddress, ActiveSwapToAddress and PublicKey to get the swap max limit - logg.InfoCtxf(ctx, "Call GetSwapFromTokenMaxLimit with:", "ActivePoolAddress", swapData.ActivePoolAddress, "ActiveSwapFromAddress", swapData.ActiveSwapFromAddress, "ActiveSwapToAddress", swapData.ActiveSwapToAddress, "publicKey", swapData.PublicKey) - r, err := h.accountService.GetSwapFromTokenMaxLimit(ctx, swapData.ActivePoolAddress, swapData.ActiveSwapFromAddress, swapData.ActiveSwapToAddress, swapData.PublicKey) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed on GetSwapFromTokenMaxLimit", "error", err) - return res, err - } - - // Scale down the amount - maxAmountStr := store.ScaleDownBalance(r.Max, swapData.ActiveSwapFromDecimal) - if err != nil { - return res, err - } - - maxAmountFloat, err := strconv.ParseFloat(maxAmountStr, 64) - if err != nil { - logg.ErrorCtxf(ctx, "failed to parse maxAmountStr as float", "value", maxAmountStr, "error", err) - return res, err - } - - // Format to 2 decimal places - maxStr := fmt.Sprintf("%.2f", maxAmountFloat) - - if maxAmountFloat < 0.1 { - // return with low amount flag - res.Content = maxStr - res.FlagSet = append(res.FlagSet, flag_low_swap_amount) - return res, nil - } - - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, []byte(maxStr)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write swap max amount entry with", "key", storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, "value", maxStr, "error", err) - return res, err - } - - res.Content = fmt.Sprintf( - "Maximum: %s\n\nEnter amount of %s to swap for %s:", - maxStr, swapData.ActiveSwapFromSym, swapData.ActiveSwapToSym, - ) - - return res, nil -} - -// SwapPreview displays the swap preview and estimates -func (h *MenuHandlers) SwapPreview(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") - } - - inputStr := string(input) - if inputStr == "0" { - return res, nil - } - - flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") - - code := codeFromCtx(ctx) - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - - userStore := h.userdataStore - - swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId) - if err != nil { - return res, err - } - - maxValue, err := strconv.ParseFloat(swapData.ActiveSwapMaxAmount, 64) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to convert the swapMaxAmount to a float", "error", err) - return res, err - } - - inputAmount, err := strconv.ParseFloat(inputStr, 64) - if err != nil || inputAmount > maxValue { - res.FlagSet = append(res.FlagSet, flag_invalid_amount) - res.Content = inputStr - return res, nil - } - - // Format the amount to 2 decimal places - formattedAmount, err := store.TruncateDecimalString(inputStr, 2) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_invalid_amount) - res.Content = inputStr - return res, nil - } - - finalAmountStr, err := store.ParseAndScaleAmount(formattedAmount, swapData.ActiveSwapFromDecimal) - if err != nil { - return res, err - } - - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT, []byte(finalAmountStr)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err) - return res, err - } - // store the user's input amount in the temporary value - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(inputStr)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err) - return res, err - } - - // call the API to get the quote - r, err := h.accountService.GetPoolSwapQuote(ctx, finalAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress) - if err != nil { - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - res.FlagSet = append(res.FlagSet, flag_api_error) - res.Content = l.Get("Your request failed. Please try again later.") - logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err) - return res, nil - } - - // Scale down the quoted amount - quoteAmountStr := store.ScaleDownBalance(r.OutValue, swapData.ActiveSwapToDecimal) - qouteAmount, err := strconv.ParseFloat(quoteAmountStr, 64) - if err != nil { - logg.ErrorCtxf(ctx, "failed to parse quoteAmountStr as float", "value", quoteAmountStr, "error", err) - return res, err - } - - // Format to 2 decimal places - qouteStr := fmt.Sprintf("%.2f", qouteAmount) - - res.Content = fmt.Sprintf( - "You will swap:\n%s %s for %s %s:", - inputStr, swapData.ActiveSwapFromSym, qouteStr, swapData.ActiveSwapToSym, - ) - - return res, nil -} - -// InitiateSwap calls the poolSwap and returns a confirmation based on the result. -func (h *MenuHandlers) InitiateSwap(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_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") - - code := codeFromCtx(ctx) - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - - userStore := h.userdataStore - - swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId) - if err != nil { - return res, err - } - - swapAmount, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read swapAmount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "error", err) - return res, err - } - - swapAmountStr := string(swapAmount) - - // Call the poolSwap API - r, err := h.accountService.PoolSwap(ctx, swapAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress) - if err != nil { - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - res.FlagSet = append(res.FlagSet, flag_api_error) - res.Content = l.Get("Your request failed. Please try again later.") - logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err) - return res, nil - } - - trackingId := r.TrackingId - logg.InfoCtxf(ctx, "poolSwap", "trackingId", trackingId) - - res.Content = l.Get( - "Your request has been sent. You will receive an SMS when your %s %s has been swapped for %s.", - swapData.TemporaryValue, - swapData.ActiveSwapFromSym, - swapData.ActiveSwapToSym, - ) - - res.FlagReset = append(res.FlagReset, flag_account_authorized) - return res, nil -} diff --git a/handlers/application/poolswap.go b/handlers/application/poolswap.go new file mode 100644 index 0000000..816dfcf --- /dev/null +++ b/handlers/application/poolswap.go @@ -0,0 +1,427 @@ +package application + +import ( + "context" + "fmt" + "strconv" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/sarafu-vise/config" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "gopkg.in/leonelquinteros/gotext.v1" +) + +// GetPools fetches a list of 5 top pools +func (h *MenuHandlers) GetPools(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") + } + userStore := h.userdataStore + + flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") + + // call the api to get a list of top 5 pools sorted by swaps + topPools, err := h.accountService.FetchTopPools(ctx) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) + return res, err + } + + // Return if there are no pools + if len(topPools) == 0 { + return res, nil + } + + data := store.ProcessPools(topPools) + + // Store all Pool data + dataMap := map[storedb.DataTyp]string{ + storedb.DATA_POOL_NAMES: data.PoolNames, + storedb.DATA_POOL_SYMBOLS: data.PoolSymbols, + storedb.DATA_POOL_ADDRESSES: data.PoolContractAdrresses, + } + + // Write data entries + for key, value := range dataMap { + if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) + continue + } + } + + res.Content = h.ReplaceSeparatorFunc(data.PoolSymbols) + + return res, nil +} + +// LoadSwapFromList returns a list of possible vouchers to swap to +func (h *MenuHandlers) LoadSwapToList(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") + } + + userStore := h.userdataStore + + // get the active address and symbol + activeAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "error", err) + return res, err + } + activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) + return res, err + } + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") + flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") + + inputStr := string(input) + if inputStr == "0" { + return res, nil + } + + // Get active pool address and symbol or fall back to default + var activePoolAddress []byte + activePoolAddress, err = userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS) + if err != nil { + if db.IsNotFound(err) { + defaultPoolAddress := config.DefaultPoolAddress() + // store the default as the active pool address + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS, []byte(defaultPoolAddress)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write default PoolContractAdrress", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "value", defaultPoolAddress, "error", err) + return res, err + } + activePoolAddress = []byte(defaultPoolAddress) + } else { + logg.ErrorCtxf(ctx, "failed to read active PoolContractAdrress", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "error", err) + return res, err + } + } + + var activePoolSymbol []byte + activePoolSymbol, err = userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM) + if err != nil { + if db.IsNotFound(err) { + defaultPoolSym := config.DefaultPoolName() + // store the default as the active pool symbol + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM, []byte(defaultPoolSym)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write default Pool Symbol", "key", storedb.DATA_ACTIVE_POOL_SYM, "value", defaultPoolSym, "error", err) + return res, err + } + activePoolSymbol = []byte(defaultPoolSym) + } else { + logg.ErrorCtxf(ctx, "failed to read active Pool symbol", "key", storedb.DATA_ACTIVE_POOL_SYM, "error", err) + return res, err + } + } + + // call the api using the ActivePoolAddress and ActiveVoucherAddress to check if it is part of the pool + r, err := h.accountService.CheckTokenInPool(ctx, string(activePoolAddress), string(activeAddress)) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on CheckTokenInPool", "error", err) + return res, err + } + + logg.InfoCtxf(ctx, "CheckTokenInPool", "response", r, "active_pool_address", string(activePoolAddress), "active_symbol_address", string(activeAddress)) + + if !r.CanSwapFrom { + res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) + res.Content = l.Get( + "%s is not in %s. Please update your voucher and try again.", + activeSym, + activePoolSymbol, + ) + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) + + // call the api using the activePoolAddress to get a list of SwapToSymbolsData + swapToList, err := h.accountService.GetPoolSwappableVouchers(ctx, string(activePoolAddress)) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) + return res, err + } + + logg.InfoCtxf(ctx, "GetPoolSwappableVouchers", "swapToList", swapToList) + + // Return if there are no vouchers + if len(swapToList) == 0 { + return res, nil + } + + data := store.ProcessVouchers(swapToList) + + logg.InfoCtxf(ctx, "ProcessVouchers", "data", data) + + // Store all swap_to tokens data + dataMap := map[storedb.DataTyp]string{ + storedb.DATA_POOL_TO_SYMBOLS: data.Symbols, + storedb.DATA_POOL_TO_BALANCES: data.Balances, + storedb.DATA_POOL_TO_DECIMALS: data.Decimals, + storedb.DATA_POOL_TO_ADDRESSES: data.Addresses, + } + + for key, value := range dataMap { + if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) + continue + } + } + + res.Content = h.ReplaceSeparatorFunc(data.Symbols) + + return res, nil +} + +// SwapMaxLimit returns the max FROM token +// check if max/tokenDecimals > 0.1 for UX purposes and to prevent swapping of dust values +func (h *MenuHandlers) SwapMaxLimit(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") + } + + flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") + flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") + flag_low_swap_amount, _ := h.flagManager.GetFlag("flag_low_swap_amount") + + res.FlagReset = append(res.FlagReset, flag_incorrect_voucher, flag_low_swap_amount) + + inputStr := string(input) + if inputStr == "0" { + return res, nil + } + + userStore := h.userdataStore + metadata, err := store.GetSwapToVoucherData(ctx, userStore, sessionId, inputStr) + if err != nil { + return res, fmt.Errorf("failed to retrieve swap to voucher data: %v", err) + } + if metadata == nil { + res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) + return res, nil + } + + logg.InfoCtxf(ctx, "Metadata from GetSwapToVoucherData:", "metadata", metadata) + + // Store the active swap_to data + if err := store.UpdateSwapToVoucherData(ctx, userStore, sessionId, metadata); err != nil { + logg.ErrorCtxf(ctx, "failed on UpdateSwapToVoucherData", "error", err) + return res, err + } + + swapData, err := store.ReadSwapData(ctx, userStore, sessionId) + if err != nil { + return res, err + } + + // call the api using the ActivePoolAddress, ActiveSwapFromAddress, ActiveSwapToAddress and PublicKey to get the swap max limit + logg.InfoCtxf(ctx, "Call GetSwapFromTokenMaxLimit with:", "ActivePoolAddress", swapData.ActivePoolAddress, "ActiveSwapFromAddress", swapData.ActiveSwapFromAddress, "ActiveSwapToAddress", swapData.ActiveSwapToAddress, "publicKey", swapData.PublicKey) + r, err := h.accountService.GetSwapFromTokenMaxLimit(ctx, swapData.ActivePoolAddress, swapData.ActiveSwapFromAddress, swapData.ActiveSwapToAddress, swapData.PublicKey) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on GetSwapFromTokenMaxLimit", "error", err) + return res, err + } + + // Scale down the amount + maxAmountStr := store.ScaleDownBalance(r.Max, swapData.ActiveSwapFromDecimal) + if err != nil { + return res, err + } + + maxAmountFloat, err := strconv.ParseFloat(maxAmountStr, 64) + if err != nil { + logg.ErrorCtxf(ctx, "failed to parse maxAmountStr as float", "value", maxAmountStr, "error", err) + return res, err + } + + // Format to 2 decimal places + maxStr := fmt.Sprintf("%.2f", maxAmountFloat) + + if maxAmountFloat < 0.1 { + // return with low amount flag + res.Content = maxStr + res.FlagSet = append(res.FlagSet, flag_low_swap_amount) + return res, nil + } + + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, []byte(maxStr)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write swap max amount entry with", "key", storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, "value", maxStr, "error", err) + return res, err + } + + res.Content = fmt.Sprintf( + "Maximum: %s\n\nEnter amount of %s to swap for %s:", + maxStr, swapData.ActiveSwapFromSym, swapData.ActiveSwapToSym, + ) + + return res, nil +} + +// SwapPreview displays the swap preview and estimates +func (h *MenuHandlers) SwapPreview(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") + } + + inputStr := string(input) + if inputStr == "0" { + return res, nil + } + + flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + userStore := h.userdataStore + + swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId) + if err != nil { + return res, err + } + + maxValue, err := strconv.ParseFloat(swapData.ActiveSwapMaxAmount, 64) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to convert the swapMaxAmount to a float", "error", err) + return res, err + } + + inputAmount, err := strconv.ParseFloat(inputStr, 64) + if err != nil || inputAmount > maxValue { + res.FlagSet = append(res.FlagSet, flag_invalid_amount) + res.Content = inputStr + return res, nil + } + + // Format the amount to 2 decimal places + formattedAmount, err := store.TruncateDecimalString(inputStr, 2) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_invalid_amount) + res.Content = inputStr + return res, nil + } + + finalAmountStr, err := store.ParseAndScaleAmount(formattedAmount, swapData.ActiveSwapFromDecimal) + if err != nil { + return res, err + } + + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT, []byte(finalAmountStr)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err) + return res, err + } + // store the user's input amount in the temporary value + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(inputStr)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err) + return res, err + } + + // call the API to get the quote + r, err := h.accountService.GetPoolSwapQuote(ctx, finalAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress) + if err != nil { + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + res.FlagSet = append(res.FlagSet, flag_api_error) + res.Content = l.Get("Your request failed. Please try again later.") + logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err) + return res, nil + } + + // Scale down the quoted amount + quoteAmountStr := store.ScaleDownBalance(r.OutValue, swapData.ActiveSwapToDecimal) + qouteAmount, err := strconv.ParseFloat(quoteAmountStr, 64) + if err != nil { + logg.ErrorCtxf(ctx, "failed to parse quoteAmountStr as float", "value", quoteAmountStr, "error", err) + return res, err + } + + // Format to 2 decimal places + qouteStr := fmt.Sprintf("%.2f", qouteAmount) + + res.Content = fmt.Sprintf( + "You will swap:\n%s %s for %s %s:", + inputStr, swapData.ActiveSwapFromSym, qouteStr, swapData.ActiveSwapToSym, + ) + + return res, nil +} + +// InitiateSwap calls the poolSwap and returns a confirmation based on the result. +func (h *MenuHandlers) InitiateSwap(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_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + userStore := h.userdataStore + + swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId) + if err != nil { + return res, err + } + + swapAmount, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read swapAmount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "error", err) + return res, err + } + + swapAmountStr := string(swapAmount) + + // Call the poolSwap API + r, err := h.accountService.PoolSwap(ctx, swapAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress) + if err != nil { + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + res.FlagSet = append(res.FlagSet, flag_api_error) + res.Content = l.Get("Your request failed. Please try again later.") + logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err) + return res, nil + } + + trackingId := r.TrackingId + logg.InfoCtxf(ctx, "poolSwap", "trackingId", trackingId) + + res.Content = l.Get( + "Your request has been sent. You will receive an SMS when your %s %s has been swapped for %s.", + swapData.TemporaryValue, + swapData.ActiveSwapFromSym, + swapData.ActiveSwapToSym, + ) + + res.FlagReset = append(res.FlagReset, flag_account_authorized) + return res, nil +} -- 2.45.2 From a6cd3d93b4479ea69a3ef5240fc47a7a6f99e17e Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 1 Jul 2025 01:55:30 +0300 Subject: [PATCH 04/26] added transactions.go for transaction histories --- handlers/application/menuhandler.go | 178 ------------------------- handlers/application/transactions.go | 190 +++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 178 deletions(-) create mode 100644 handlers/application/transactions.go diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 56a80fa..827659d 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -1919,184 +1919,6 @@ func (h *MenuHandlers) SetPool(ctx context.Context, sym string, input []byte) (r return res, nil } -// CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb. -func (h *MenuHandlers) CheckTransactions(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") - } - - flag_no_transfers, _ := h.flagManager.GetFlag("flag_no_transfers") - flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") - - userStore := h.userdataStore - logdb := h.logDb - publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } - - // Fetch transactions from the API using the public key - transactionsResp, err := h.accountService.FetchTransactions(ctx, string(publicKey)) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) - return res, err - } - res.FlagReset = append(res.FlagReset, flag_api_error) - - // Return if there are no transactions - if len(transactionsResp) == 0 { - res.FlagSet = append(res.FlagSet, flag_no_transfers) - return res, nil - } - - data := store.ProcessTransfers(transactionsResp) - - // Store all transaction data - dataMap := map[storedb.DataTyp]string{ - storedb.DATA_TX_SENDERS: data.Senders, - storedb.DATA_TX_RECIPIENTS: data.Recipients, - storedb.DATA_TX_VALUES: data.TransferValues, - storedb.DATA_TX_ADDRESSES: data.Addresses, - storedb.DATA_TX_HASHES: data.TxHashes, - storedb.DATA_TX_DATES: data.Dates, - storedb.DATA_TX_SYMBOLS: data.Symbols, - storedb.DATA_TX_DECIMALS: data.Decimals, - } - - for key, value := range dataMap { - if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil { - logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err) - return res, err - } - err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write tx db log entry", "key", key, "value", value) - } - } - - res.FlagReset = append(res.FlagReset, flag_no_transfers) - - return res, nil -} - -// GetTransactionsList fetches the list of transactions and formats them. -func (h *MenuHandlers) GetTransactionsList(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") - } - - userStore := h.userdataStore - publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } - - // Read transactions from the store and format them - TransactionSenders, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SENDERS)) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to read the TransactionSenders from prefixDb", "error", err) - return res, err - } - TransactionSyms, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SYMBOLS)) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to read the TransactionSyms from prefixDb", "error", err) - return res, err - } - TransactionValues, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_VALUES)) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to read the TransactionValues from prefixDb", "error", err) - return res, err - } - TransactionDates, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_DATES)) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err) - return res, err - } - - // Parse the data - senders := strings.Split(string(TransactionSenders), "\n") - syms := strings.Split(string(TransactionSyms), "\n") - values := strings.Split(string(TransactionValues), "\n") - dates := strings.Split(string(TransactionDates), "\n") - - var formattedTransactions []string - for i := 0; i < len(senders); i++ { - sender := strings.TrimSpace(senders[i]) - sym := strings.TrimSpace(syms[i]) - value := strings.TrimSpace(values[i]) - date := strings.Split(strings.TrimSpace(dates[i]), " ")[0] - - status := "Received" - if sender == string(publicKey) { - status = "Sent" - } - - // Use the ReplaceSeparator function for the menu separator - transactionLine := fmt.Sprintf("%d%s%s %s %s %s", i+1, h.ReplaceSeparatorFunc(":"), status, value, sym, date) - formattedTransactions = append(formattedTransactions, transactionLine) - } - - res.Content = strings.Join(formattedTransactions, "\n") - - return res, nil -} - -// ViewTransactionStatement retrieves the transaction statement -// and displays it to the user. -func (h *MenuHandlers) ViewTransactionStatement(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") - } - userStore := h.userdataStore - publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } - - flag_incorrect_statement, _ := h.flagManager.GetFlag("flag_incorrect_statement") - - inputStr := string(input) - if inputStr == "0" || inputStr == "99" || inputStr == "11" || inputStr == "22" { - res.FlagReset = append(res.FlagReset, flag_incorrect_statement) - return res, nil - } - - // Convert input string to integer - index, err := strconv.Atoi(strings.TrimSpace(inputStr)) - if err != nil { - return res, fmt.Errorf("invalid input: must be a number between 1 and 10") - } - - if index < 1 || index > 10 { - return res, fmt.Errorf("invalid input: index must be between 1 and 10") - } - - statement, err := store.GetTransferData(ctx, h.prefixDb, string(publicKey), index) - if err != nil { - return res, fmt.Errorf("failed to retrieve transfer data: %v", err) - } - - if statement == "" { - res.FlagSet = append(res.FlagSet, flag_incorrect_statement) - return res, nil - } - - res.FlagReset = append(res.FlagReset, flag_incorrect_statement) - res.Content = statement - - return res, nil -} - // persistInitialLanguageCode receives an initial language code and persists it to the store func (h *MenuHandlers) persistInitialLanguageCode(ctx context.Context, sessionId string, code string) error { store := h.userdataStore diff --git a/handlers/application/transactions.go b/handlers/application/transactions.go new file mode 100644 index 0000000..62d2a45 --- /dev/null +++ b/handlers/application/transactions.go @@ -0,0 +1,190 @@ +package application + +import ( + "context" + "fmt" + "strconv" + "strings" + + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" +) + +// CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb. +func (h *MenuHandlers) CheckTransactions(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") + } + + flag_no_transfers, _ := h.flagManager.GetFlag("flag_no_transfers") + flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") + + userStore := h.userdataStore + logdb := h.logDb + publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) + return res, err + } + + // Fetch transactions from the API using the public key + transactionsResp, err := h.accountService.FetchTransactions(ctx, string(publicKey)) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) + return res, err + } + res.FlagReset = append(res.FlagReset, flag_api_error) + + // Return if there are no transactions + if len(transactionsResp) == 0 { + res.FlagSet = append(res.FlagSet, flag_no_transfers) + return res, nil + } + + data := store.ProcessTransfers(transactionsResp) + + // Store all transaction data + dataMap := map[storedb.DataTyp]string{ + storedb.DATA_TX_SENDERS: data.Senders, + storedb.DATA_TX_RECIPIENTS: data.Recipients, + storedb.DATA_TX_VALUES: data.TransferValues, + storedb.DATA_TX_ADDRESSES: data.Addresses, + storedb.DATA_TX_HASHES: data.TxHashes, + storedb.DATA_TX_DATES: data.Dates, + storedb.DATA_TX_SYMBOLS: data.Symbols, + storedb.DATA_TX_DECIMALS: data.Decimals, + } + + for key, value := range dataMap { + if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil { + logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err) + return res, err + } + err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write tx db log entry", "key", key, "value", value) + } + } + + res.FlagReset = append(res.FlagReset, flag_no_transfers) + + return res, nil +} + +// GetTransactionsList fetches the list of transactions and formats them. +func (h *MenuHandlers) GetTransactionsList(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") + } + + userStore := h.userdataStore + publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) + return res, err + } + + // Read transactions from the store and format them + TransactionSenders, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SENDERS)) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to read the TransactionSenders from prefixDb", "error", err) + return res, err + } + TransactionSyms, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SYMBOLS)) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to read the TransactionSyms from prefixDb", "error", err) + return res, err + } + TransactionValues, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_VALUES)) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to read the TransactionValues from prefixDb", "error", err) + return res, err + } + TransactionDates, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_DATES)) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err) + return res, err + } + + // Parse the data + senders := strings.Split(string(TransactionSenders), "\n") + syms := strings.Split(string(TransactionSyms), "\n") + values := strings.Split(string(TransactionValues), "\n") + dates := strings.Split(string(TransactionDates), "\n") + + var formattedTransactions []string + for i := 0; i < len(senders); i++ { + sender := strings.TrimSpace(senders[i]) + sym := strings.TrimSpace(syms[i]) + value := strings.TrimSpace(values[i]) + date := strings.Split(strings.TrimSpace(dates[i]), " ")[0] + + status := "Received" + if sender == string(publicKey) { + status = "Sent" + } + + // Use the ReplaceSeparator function for the menu separator + transactionLine := fmt.Sprintf("%d%s%s %s %s %s", i+1, h.ReplaceSeparatorFunc(":"), status, value, sym, date) + formattedTransactions = append(formattedTransactions, transactionLine) + } + + res.Content = strings.Join(formattedTransactions, "\n") + + return res, nil +} + +// ViewTransactionStatement retrieves the transaction statement +// and displays it to the user. +func (h *MenuHandlers) ViewTransactionStatement(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") + } + userStore := h.userdataStore + publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) + return res, err + } + + flag_incorrect_statement, _ := h.flagManager.GetFlag("flag_incorrect_statement") + + inputStr := string(input) + if inputStr == "0" || inputStr == "99" || inputStr == "11" || inputStr == "22" { + res.FlagReset = append(res.FlagReset, flag_incorrect_statement) + return res, nil + } + + // Convert input string to integer + index, err := strconv.Atoi(strings.TrimSpace(inputStr)) + if err != nil { + return res, fmt.Errorf("invalid input: must be a number between 1 and 10") + } + + if index < 1 || index > 10 { + return res, fmt.Errorf("invalid input: index must be between 1 and 10") + } + + statement, err := store.GetTransferData(ctx, h.prefixDb, string(publicKey), index) + if err != nil { + return res, fmt.Errorf("failed to retrieve transfer data: %v", err) + } + + if statement == "" { + res.FlagSet = append(res.FlagSet, flag_incorrect_statement) + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_incorrect_statement) + res.Content = statement + + return res, nil +} -- 2.45.2 From 7b85451fd6b62c87bcda737ca257d93a2a32bc4a Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 1 Jul 2025 01:57:32 +0300 Subject: [PATCH 05/26] added vouchers.go --- handlers/application/menuhandler.go | 250 -------------------------- handlers/application/vouchers.go | 262 ++++++++++++++++++++++++++++ 2 files changed, 262 insertions(+), 250 deletions(-) create mode 100644 handlers/application/vouchers.go diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 827659d..4213c0c 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -33,7 +33,6 @@ import ( "git.grassecon.net/grassrootseconomics/sarafu-vise/store" storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" "github.com/grassrootseconomics/ethutils" - dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) var ( @@ -1566,255 +1565,6 @@ func (h *MenuHandlers) InitiateTransaction(ctx context.Context, sym string, inpu return res, nil } -// ManageVouchers retrieves the token holdings from the API using the "PublicKey" and -// 1. sets the first as the default voucher if no active voucher is set. -// 2. Stores list of vouchers -// 3. updates the balance of the active voucher -func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - userStore := h.userdataStore - logdb := h.logDb - - 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") - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - - publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read publicKey entry", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } - - // Fetch vouchers from API - vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey)) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - return res, nil - } - res.FlagReset = append(res.FlagReset, flag_api_error) - - if len(vouchersResp) == 0 { - res.FlagSet = append(res.FlagSet, flag_no_active_voucher) - return res, nil - } - - res.FlagReset = append(res.FlagReset, flag_no_active_voucher) - - // Check if user has an active voucher with proper error handling - activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) - if err != nil { - if db.IsNotFound(err) { - // No active voucher, set the first one as default - firstVoucher := vouchersResp[0] - defaultSym := firstVoucher.TokenSymbol - defaultBal := firstVoucher.Balance - defaultDec := firstVoucher.TokenDecimals - defaultAddr := firstVoucher.TokenAddress - - // Scale down the balance - scaledBalance := store.ScaleDownBalance(defaultBal, defaultDec) - - firstVoucherMap := map[storedb.DataTyp]string{ - storedb.DATA_ACTIVE_SYM: defaultSym, - storedb.DATA_ACTIVE_BAL: scaledBalance, - storedb.DATA_ACTIVE_DECIMAL: defaultDec, - storedb.DATA_ACTIVE_ADDRESS: defaultAddr, - } - - for key, value := range firstVoucherMap { - if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { - logg.ErrorCtxf(ctx, "Failed to write active voucher data", "key", key, "error", err) - return res, err - } - err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write voucher db log entry", "key", key, "value", value) - } - } - - logg.InfoCtxf(ctx, "Default voucher set", "symbol", defaultSym, "balance", defaultBal, "decimals", defaultDec, "address", defaultAddr) - } else { - logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) - return res, err - } - } else { - // Update active voucher balance - activeSymStr := string(activeSym) - - // Find the matching voucher data - var activeData *dataserviceapi.TokenHoldings - for _, voucher := range vouchersResp { - if voucher.TokenSymbol == activeSymStr { - activeData = &voucher - break - } - } - - if activeData == nil { - logg.ErrorCtxf(ctx, "activeSym not found in vouchers", "activeSym", activeSymStr) - return res, fmt.Errorf("activeSym %s not found in vouchers", activeSymStr) - } - - // Scale down the balance - scaledBalance := store.ScaleDownBalance(activeData.Balance, activeData.TokenDecimals) - - // Update the balance field with the scaled value - activeData.Balance = scaledBalance - - // Pass the matching voucher data to UpdateVoucherData - if err := store.UpdateVoucherData(ctx, h.userdataStore, sessionId, activeData); err != nil { - logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err) - return res, err - } - } - - // Store all voucher data - data := store.ProcessVouchers(vouchersResp) - dataMap := map[storedb.DataTyp]string{ - storedb.DATA_VOUCHER_SYMBOLS: data.Symbols, - storedb.DATA_VOUCHER_BALANCES: data.Balances, - storedb.DATA_VOUCHER_DECIMALS: data.Decimals, - storedb.DATA_VOUCHER_ADDRESSES: data.Addresses, - } - - // Write data entries - for key, value := range dataMap { - if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { - logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) - continue - } - } - - return res, nil -} - -// GetVoucherList fetches the list of vouchers and formats them. -func (h *MenuHandlers) GetVoucherList(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") - } - - userStore := h.userdataStore - - // Read vouchers from the store - voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS) - logg.InfoCtxf(ctx, "reading GetVoucherList entries for sessionId: %s", sessionId, "key", storedb.DATA_VOUCHER_SYMBOLS, "voucherData", voucherData) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read voucherData entires with", "key", storedb.DATA_VOUCHER_SYMBOLS, "error", err) - return res, err - } - - formattedData := h.ReplaceSeparatorFunc(string(voucherData)) - - logg.InfoCtxf(ctx, "final output for sessionId: %s", sessionId, "key", storedb.DATA_VOUCHER_SYMBOLS, "formattedData", formattedData) - - res.Content = string(formattedData) - - return res, nil -} - -// ViewVoucher retrieves the token holding and balance from the subprefixDB -// and displays it to the user for them to select it. -func (h *MenuHandlers) ViewVoucher(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") - } - - code := codeFromCtx(ctx) - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - - flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") - - inputStr := string(input) - - metadata, err := store.GetVoucherData(ctx, h.userdataStore, sessionId, inputStr) - if err != nil { - return res, fmt.Errorf("failed to retrieve voucher data: %v", err) - } - - if metadata == nil { - res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) - return res, nil - } - - if err := store.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil { - logg.ErrorCtxf(ctx, "failed on StoreTemporaryVoucher", "error", err) - return res, err - } - - res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) - res.Content = l.Get("Symbol: %s\nBalance: %s", metadata.TokenSymbol, metadata.Balance) - - return res, nil -} - -// SetVoucher retrieves the temp voucher data and sets it as the active data. -func (h *MenuHandlers) SetVoucher(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") - } - - // Get temporary data - tempData, err := store.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId) - if err != nil { - logg.ErrorCtxf(ctx, "failed on GetTemporaryVoucherData", "error", err) - return res, err - } - - // Set as active and clear temporary data - if err := store.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil { - logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err) - return res, err - } - - res.Content = tempData.TokenSymbol - return res, nil -} - -// GetVoucherDetails retrieves the voucher details. -func (h *MenuHandlers) GetVoucherDetails(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_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - - // get the active address - activeAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "error", err) - return res, err - } - - // use the voucher contract address to get the data from the API - voucherData, err := h.accountService.VoucherData(ctx, string(activeAddress)) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - return res, nil - } - res.FlagReset = append(res.FlagReset, flag_api_error) - - res.Content = fmt.Sprintf( - "Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", voucherData.TokenName, voucherData.TokenSymbol, voucherData.TokenCommodity, voucherData.TokenLocation, - ) - - return res, nil -} - // GetDefaultPool returns the current user's Pool. If none is set, it returns the default config pool. func (h *MenuHandlers) GetDefaultPool(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result diff --git a/handlers/application/vouchers.go b/handlers/application/vouchers.go new file mode 100644 index 0000000..5db14f3 --- /dev/null +++ b/handlers/application/vouchers.go @@ -0,0 +1,262 @@ +package application + +import ( + "context" + "fmt" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" + "gopkg.in/leonelquinteros/gotext.v1" +) + +// ManageVouchers retrieves the token holdings from the API using the "PublicKey" and +// 1. sets the first as the default voucher if no active voucher is set. +// 2. Stores list of vouchers +// 3. updates the balance of the active voucher +func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + userStore := h.userdataStore + logdb := h.logDb + + 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") + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + + publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read publicKey entry", "key", storedb.DATA_PUBLIC_KEY, "error", err) + return res, err + } + + // Fetch vouchers from API + vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey)) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + return res, nil + } + res.FlagReset = append(res.FlagReset, flag_api_error) + + if len(vouchersResp) == 0 { + res.FlagSet = append(res.FlagSet, flag_no_active_voucher) + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_no_active_voucher) + + // Check if user has an active voucher with proper error handling + activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) + if err != nil { + if db.IsNotFound(err) { + // No active voucher, set the first one as default + firstVoucher := vouchersResp[0] + defaultSym := firstVoucher.TokenSymbol + defaultBal := firstVoucher.Balance + defaultDec := firstVoucher.TokenDecimals + defaultAddr := firstVoucher.TokenAddress + + // Scale down the balance + scaledBalance := store.ScaleDownBalance(defaultBal, defaultDec) + + firstVoucherMap := map[storedb.DataTyp]string{ + storedb.DATA_ACTIVE_SYM: defaultSym, + storedb.DATA_ACTIVE_BAL: scaledBalance, + storedb.DATA_ACTIVE_DECIMAL: defaultDec, + storedb.DATA_ACTIVE_ADDRESS: defaultAddr, + } + + for key, value := range firstVoucherMap { + if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write active voucher data", "key", key, "error", err) + return res, err + } + err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write voucher db log entry", "key", key, "value", value) + } + } + + logg.InfoCtxf(ctx, "Default voucher set", "symbol", defaultSym, "balance", defaultBal, "decimals", defaultDec, "address", defaultAddr) + } else { + logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) + return res, err + } + } else { + // Update active voucher balance + activeSymStr := string(activeSym) + + // Find the matching voucher data + var activeData *dataserviceapi.TokenHoldings + for _, voucher := range vouchersResp { + if voucher.TokenSymbol == activeSymStr { + activeData = &voucher + break + } + } + + if activeData == nil { + logg.ErrorCtxf(ctx, "activeSym not found in vouchers", "activeSym", activeSymStr) + return res, fmt.Errorf("activeSym %s not found in vouchers", activeSymStr) + } + + // Scale down the balance + scaledBalance := store.ScaleDownBalance(activeData.Balance, activeData.TokenDecimals) + + // Update the balance field with the scaled value + activeData.Balance = scaledBalance + + // Pass the matching voucher data to UpdateVoucherData + if err := store.UpdateVoucherData(ctx, h.userdataStore, sessionId, activeData); err != nil { + logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err) + return res, err + } + } + + // Store all voucher data + data := store.ProcessVouchers(vouchersResp) + dataMap := map[storedb.DataTyp]string{ + storedb.DATA_VOUCHER_SYMBOLS: data.Symbols, + storedb.DATA_VOUCHER_BALANCES: data.Balances, + storedb.DATA_VOUCHER_DECIMALS: data.Decimals, + storedb.DATA_VOUCHER_ADDRESSES: data.Addresses, + } + + // Write data entries + for key, value := range dataMap { + if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) + continue + } + } + + return res, nil +} + +// GetVoucherList fetches the list of vouchers and formats them. +func (h *MenuHandlers) GetVoucherList(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") + } + + userStore := h.userdataStore + + // Read vouchers from the store + voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS) + logg.InfoCtxf(ctx, "reading GetVoucherList entries for sessionId: %s", sessionId, "key", storedb.DATA_VOUCHER_SYMBOLS, "voucherData", voucherData) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read voucherData entires with", "key", storedb.DATA_VOUCHER_SYMBOLS, "error", err) + return res, err + } + + formattedData := h.ReplaceSeparatorFunc(string(voucherData)) + + logg.InfoCtxf(ctx, "final output for sessionId: %s", sessionId, "key", storedb.DATA_VOUCHER_SYMBOLS, "formattedData", formattedData) + + res.Content = string(formattedData) + + return res, nil +} + +// ViewVoucher retrieves the token holding and balance from the subprefixDB +// and displays it to the user for them to select it. +func (h *MenuHandlers) ViewVoucher(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") + } + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") + + inputStr := string(input) + + metadata, err := store.GetVoucherData(ctx, h.userdataStore, sessionId, inputStr) + if err != nil { + return res, fmt.Errorf("failed to retrieve voucher data: %v", err) + } + + if metadata == nil { + res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) + return res, nil + } + + if err := store.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil { + logg.ErrorCtxf(ctx, "failed on StoreTemporaryVoucher", "error", err) + return res, err + } + + res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) + res.Content = l.Get("Symbol: %s\nBalance: %s", metadata.TokenSymbol, metadata.Balance) + + return res, nil +} + +// SetVoucher retrieves the temp voucher data and sets it as the active data. +func (h *MenuHandlers) SetVoucher(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") + } + + // Get temporary data + tempData, err := store.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId) + if err != nil { + logg.ErrorCtxf(ctx, "failed on GetTemporaryVoucherData", "error", err) + return res, err + } + + // Set as active and clear temporary data + if err := store.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil { + logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err) + return res, err + } + + res.Content = tempData.TokenSymbol + return res, nil +} + +// GetVoucherDetails retrieves the voucher details. +func (h *MenuHandlers) GetVoucherDetails(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_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + + // get the active address + activeAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "error", err) + return res, err + } + + // use the voucher contract address to get the data from the API + voucherData, err := h.accountService.VoucherData(ctx, string(activeAddress)) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + return res, nil + } + res.FlagReset = append(res.FlagReset, flag_api_error) + + res.Content = fmt.Sprintf( + "Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", voucherData.TokenName, voucherData.TokenSymbol, voucherData.TokenCommodity, voucherData.TokenLocation, + ) + + return res, nil +} -- 2.45.2 From 164ce5ea5a5e7cb515218a417e409a6ff78ee908 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 1 Jul 2025 01:59:20 +0300 Subject: [PATCH 06/26] added upsell.go for inviting valid users --- handlers/application/menuhandler.go | 35 ---------------------- handlers/application/upsell.go | 46 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 35 deletions(-) create mode 100644 handlers/application/upsell.go diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 4213c0c..f334ac3 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -1318,41 +1318,6 @@ func (h *MenuHandlers) TransactionReset(ctx context.Context, sym string, input [ return res, nil } -// InviteValidRecipient sends an invitation to the valid phone number. -func (h *MenuHandlers) InviteValidRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - store := h.userdataStore - smsservice := h.smsService - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - code := codeFromCtx(ctx) - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - - recipient, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to read invalid recipient info", "error", err) - return res, err - } - - if !phone.IsValidPhoneNumber(string(recipient)) { - logg.InfoCtxf(ctx, "corrupted recipient", "key", storedb.DATA_TEMPORARY_VALUE, "recipient", recipient) - return res, nil - } - - _, err = smsservice.Accountservice.SendUpsellSMS(ctx, sessionId, string(recipient)) - if err != nil { - res.Content = l.Get("Your invite request for %s to Sarafu Network failed. Please try again later.", string(recipient)) - return res, nil - } - res.Content = l.Get("Your invitation to %s to join Sarafu Network has been sent.", string(recipient)) - return res, nil -} - // ResetTransactionAmount resets the transaction amount and invalid flag. func (h *MenuHandlers) ResetTransactionAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result diff --git a/handlers/application/upsell.go b/handlers/application/upsell.go new file mode 100644 index 0000000..843515d --- /dev/null +++ b/handlers/application/upsell.go @@ -0,0 +1,46 @@ +package application + +import ( + "context" + "fmt" + + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/common/phone" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "gopkg.in/leonelquinteros/gotext.v1" +) + +// InviteValidRecipient sends an invitation to the valid phone number. +func (h *MenuHandlers) InviteValidRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + store := h.userdataStore + smsservice := h.smsService + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + recipient, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to read invalid recipient info", "error", err) + return res, err + } + + if !phone.IsValidPhoneNumber(string(recipient)) { + logg.InfoCtxf(ctx, "corrupted recipient", "key", storedb.DATA_TEMPORARY_VALUE, "recipient", recipient) + return res, nil + } + + _, err = smsservice.Accountservice.SendUpsellSMS(ctx, sessionId, string(recipient)) + if err != nil { + res.Content = l.Get("Your invite request for %s to Sarafu Network failed. Please try again later.", string(recipient)) + return res, nil + } + res.Content = l.Get("Your invitation to %s to join Sarafu Network has been sent.", string(recipient)) + return res, nil +} -- 2.45.2 From 857964f69a02c945a03f9b60c0c93cd4b498a52f Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 1 Jul 2025 02:01:45 +0300 Subject: [PATCH 07/26] added send.go for voucher transfers --- handlers/application/menuhandler.go | 365 -------------------------- handlers/application/send.go | 380 ++++++++++++++++++++++++++++ 2 files changed, 380 insertions(+), 365 deletions(-) create mode 100644 handlers/application/send.go diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index f334ac3..77885e0 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -20,19 +20,15 @@ import ( "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/state" - "git.grassecon.net/grassrootseconomics/common/identity" commonlang "git.grassecon.net/grassrootseconomics/common/lang" "git.grassecon.net/grassrootseconomics/common/person" - "git.grassecon.net/grassrootseconomics/common/phone" "git.grassecon.net/grassrootseconomics/common/pin" - "git.grassecon.net/grassrootseconomics/sarafu-api/models" "git.grassecon.net/grassrootseconomics/sarafu-api/remote" "git.grassecon.net/grassrootseconomics/sarafu-vise/config" "git.grassecon.net/grassrootseconomics/sarafu-vise/internal/sms" "git.grassecon.net/grassrootseconomics/sarafu-vise/profile" "git.grassecon.net/grassrootseconomics/sarafu-vise/store" storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" - "github.com/grassrootseconomics/ethutils" ) var ( @@ -1169,367 +1165,6 @@ func (h *MenuHandlers) FetchCommunityBalance(ctx context.Context, sym string, in return res, nil } -// ValidateRecipient validates that the given input is valid. -// -// TODO: split up functino -func (h *MenuHandlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - var AliasAddressResult string - var AliasAddress *models.AliasAddress - store := h.userdataStore - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") - flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - - // remove white spaces - recipient := strings.ReplaceAll(string(input), " ", "") - - if recipient != "0" { - recipientType, err := identity.CheckRecipient(recipient) - if err != nil { - // Invalid recipient format (not a phone number, address, or valid alias format) - res.FlagSet = append(res.FlagSet, flag_invalid_recipient) - res.Content = recipient - - return res, nil - } - - // save the recipient as the temporaryRecipient - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(recipient)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryRecipient entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", recipient, "error", err) - return res, err - } - - switch recipientType { - case "phone number": - // format the phone number - formattedNumber, err := phone.FormatPhoneNumber(recipient) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", recipient, "error", err) - return res, err - } - - // Check if the phone number is registered - publicKey, err := store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY) - if err != nil { - if db.IsNotFound(err) { - logg.InfoCtxf(ctx, "Unregistered phone number: %s", recipient) - res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite) - res.Content = recipient - return res, nil - } - - logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } - - // Save the publicKey as the recipient - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, publicKey) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", string(publicKey), "error", err) - return res, err - } - - case "address": - // checksum the address - address := ethutils.ChecksumAddress(recipient) - - // Save the valid Ethereum address as the recipient - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(address)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", recipient, "error", err) - return res, err - } - - case "alias": - if strings.Contains(recipient, ".") { - AliasAddress, err = h.accountService.CheckAliasAddress(ctx, recipient) - if err == nil { - AliasAddressResult = AliasAddress.Address - } else { - logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err) - } - } else { - //Perform a search for each search domain,break on first match - for _, domain := range config.SearchDomains() { - fqdn := fmt.Sprintf("%s.%s", recipient, domain) - logg.InfoCtxf(ctx, "Resolving with fqdn alias", "alias", fqdn) - AliasAddress, err = h.accountService.CheckAliasAddress(ctx, fqdn) - if err == nil { - res.FlagReset = append(res.FlagReset, flag_api_error) - AliasAddressResult = AliasAddress.Address - continue - } else { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err) - return res, nil - } - } - } - if AliasAddressResult == "" { - res.Content = recipient - res.FlagSet = append(res.FlagSet, flag_invalid_recipient) - return res, nil - } else { - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(AliasAddressResult)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", AliasAddressResult, "error", err) - return res, err - } - } - } - } - - return res, nil -} - -// TransactionReset resets the previous transaction data (Recipient and Amount) -// as well as the invalid flags. -func (h *MenuHandlers) TransactionReset(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_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") - flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") - store := h.userdataStore - err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte("")) - if err != nil { - return res, nil - } - - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte("")) - if err != nil { - return res, nil - } - - res.FlagReset = append(res.FlagReset, flag_invalid_recipient, flag_invalid_recipient_with_invite) - - return res, nil -} - -// ResetTransactionAmount resets the transaction amount and invalid flag. -func (h *MenuHandlers) ResetTransactionAmount(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") - store := h.userdataStore - err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte("")) - if err != nil { - return res, nil - } - - res.FlagReset = append(res.FlagReset, flag_invalid_amount) - - return res, nil -} - -// MaxAmount gets the current balance from the API and sets it as -// the result content. -func (h *MenuHandlers) MaxAmount(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") - } - store := h.userdataStore - - activeBal, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err) - return res, err - } - - res.Content = string(activeBal) - - return res, nil -} - -// ValidateAmount ensures that the given input is a valid amount and that -// it is not more than the current balance. -func (h *MenuHandlers) ValidateAmount(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") - } - flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") - userStore := h.userdataStore - - var balanceValue float64 - - // retrieve the active balance - activeBal, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err) - return res, err - } - balanceValue, err = strconv.ParseFloat(string(activeBal), 64) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to convert the activeBal to a float", "error", err) - return res, err - } - - // 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 - return res, nil - } - - if inputAmount > balanceValue { - res.FlagSet = append(res.FlagSet, flag_invalid_amount) - res.Content = amountStr - return res, nil - } - - // Format the amount to 2 decimal places before saving (truncated) - formattedAmount, err := store.TruncateDecimalString(amountStr, 2) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_invalid_amount) - res.Content = amountStr - return res, nil - } - - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(formattedAmount)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write amount entry with", "key", storedb.DATA_AMOUNT, "value", formattedAmount, "error", err) - return res, err - } - - res.Content = formattedAmount - return res, nil -} - -// GetRecipient returns the transaction recipient phone number from the gdbm. -func (h *MenuHandlers) GetRecipient(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 - recipient, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if len(recipient) == 0 { - logg.ErrorCtxf(ctx, "recipient is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - - res.Content = string(recipient) - - return res, nil -} - -// GetSender returns the sessionId (phoneNumber). -func (h *MenuHandlers) GetSender(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") - } - - res.Content = sessionId - - return res, nil -} - -// GetAmount retrieves the amount from teh Gdbm Db. -func (h *MenuHandlers) GetAmount(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 - - // retrieve the active symbol - activeSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) - return res, err - } - - amount, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_AMOUNT) - - res.Content = fmt.Sprintf("%s %s", string(amount), string(activeSym)) - - return res, nil -} - -// InitiateTransaction calls the TokenTransfer and returns a confirmation based on the result. -func (h *MenuHandlers) InitiateTransaction(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_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") - - code := codeFromCtx(ctx) - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - - data, err := store.ReadTransactionData(ctx, h.userdataStore, sessionId) - if err != nil { - return res, err - } - - finalAmountStr, err := store.ParseAndScaleAmount(data.Amount, data.ActiveDecimal) - if err != nil { - return res, err - } - - // Call TokenTransfer - r, err := h.accountService.TokenTransfer(ctx, finalAmountStr, data.PublicKey, data.Recipient, data.ActiveAddress) - if err != nil { - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - res.FlagSet = append(res.FlagSet, flag_api_error) - res.Content = l.Get("Your request failed. Please try again later.") - logg.ErrorCtxf(ctx, "failed on TokenTransfer", "error", err) - return res, nil - } - - trackingId := r.TrackingId - logg.InfoCtxf(ctx, "TokenTransfer", "trackingId", trackingId) - - res.Content = l.Get( - "Your request has been sent. %s will receive %s %s from %s.", - data.TemporaryValue, - data.Amount, - data.ActiveSym, - sessionId, - ) - - res.FlagReset = append(res.FlagReset, flag_account_authorized) - return res, nil -} - // GetDefaultPool returns the current user's Pool. If none is set, it returns the default config pool. func (h *MenuHandlers) GetDefaultPool(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result diff --git a/handlers/application/send.go b/handlers/application/send.go new file mode 100644 index 0000000..3580288 --- /dev/null +++ b/handlers/application/send.go @@ -0,0 +1,380 @@ +package application + +import ( + "context" + "fmt" + "strconv" + "strings" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/common/identity" + "git.grassecon.net/grassrootseconomics/common/phone" + "git.grassecon.net/grassrootseconomics/sarafu-api/models" + "git.grassecon.net/grassrootseconomics/sarafu-vise/config" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/grassrootseconomics/ethutils" + "gopkg.in/leonelquinteros/gotext.v1" +) + +// ValidateRecipient validates that the given input is valid. +// +// TODO: split up functino +func (h *MenuHandlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + var AliasAddressResult string + var AliasAddress *models.AliasAddress + store := h.userdataStore + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") + flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + + // remove white spaces + recipient := strings.ReplaceAll(string(input), " ", "") + + if recipient != "0" { + recipientType, err := identity.CheckRecipient(recipient) + if err != nil { + // Invalid recipient format (not a phone number, address, or valid alias format) + res.FlagSet = append(res.FlagSet, flag_invalid_recipient) + res.Content = recipient + + return res, nil + } + + // save the recipient as the temporaryRecipient + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(recipient)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporaryRecipient entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", recipient, "error", err) + return res, err + } + + switch recipientType { + case "phone number": + // format the phone number + formattedNumber, err := phone.FormatPhoneNumber(recipient) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", recipient, "error", err) + return res, err + } + + // Check if the phone number is registered + publicKey, err := store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY) + if err != nil { + if db.IsNotFound(err) { + logg.InfoCtxf(ctx, "Unregistered phone number: %s", recipient) + res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite) + res.Content = recipient + return res, nil + } + + logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) + return res, err + } + + // Save the publicKey as the recipient + err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, publicKey) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", string(publicKey), "error", err) + return res, err + } + + case "address": + // checksum the address + address := ethutils.ChecksumAddress(recipient) + + // Save the valid Ethereum address as the recipient + err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(address)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", recipient, "error", err) + return res, err + } + + case "alias": + if strings.Contains(recipient, ".") { + AliasAddress, err = h.accountService.CheckAliasAddress(ctx, recipient) + if err == nil { + AliasAddressResult = AliasAddress.Address + } else { + logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err) + } + } else { + //Perform a search for each search domain,break on first match + for _, domain := range config.SearchDomains() { + fqdn := fmt.Sprintf("%s.%s", recipient, domain) + logg.InfoCtxf(ctx, "Resolving with fqdn alias", "alias", fqdn) + AliasAddress, err = h.accountService.CheckAliasAddress(ctx, fqdn) + if err == nil { + res.FlagReset = append(res.FlagReset, flag_api_error) + AliasAddressResult = AliasAddress.Address + continue + } else { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err) + return res, nil + } + } + } + if AliasAddressResult == "" { + res.Content = recipient + res.FlagSet = append(res.FlagSet, flag_invalid_recipient) + return res, nil + } else { + err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(AliasAddressResult)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", AliasAddressResult, "error", err) + return res, err + } + } + } + } + + return res, nil +} + +// TransactionReset resets the previous transaction data (Recipient and Amount) +// as well as the invalid flags. +func (h *MenuHandlers) TransactionReset(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_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") + flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") + store := h.userdataStore + err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte("")) + if err != nil { + return res, nil + } + + err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte("")) + if err != nil { + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_invalid_recipient, flag_invalid_recipient_with_invite) + + return res, nil +} + +// ResetTransactionAmount resets the transaction amount and invalid flag. +func (h *MenuHandlers) ResetTransactionAmount(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") + store := h.userdataStore + err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte("")) + if err != nil { + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_invalid_amount) + + return res, nil +} + +// MaxAmount gets the current balance from the API and sets it as +// the result content. +func (h *MenuHandlers) MaxAmount(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") + } + store := h.userdataStore + + activeBal, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err) + return res, err + } + + res.Content = string(activeBal) + + return res, nil +} + +// ValidateAmount ensures that the given input is a valid amount and that +// it is not more than the current balance. +func (h *MenuHandlers) ValidateAmount(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") + } + flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") + userStore := h.userdataStore + + var balanceValue float64 + + // retrieve the active balance + activeBal, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err) + return res, err + } + balanceValue, err = strconv.ParseFloat(string(activeBal), 64) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to convert the activeBal to a float", "error", err) + return res, err + } + + // 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 + return res, nil + } + + if inputAmount > balanceValue { + res.FlagSet = append(res.FlagSet, flag_invalid_amount) + res.Content = amountStr + return res, nil + } + + // Format the amount to 2 decimal places before saving (truncated) + formattedAmount, err := store.TruncateDecimalString(amountStr, 2) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_invalid_amount) + res.Content = amountStr + return res, nil + } + + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(formattedAmount)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write amount entry with", "key", storedb.DATA_AMOUNT, "value", formattedAmount, "error", err) + return res, err + } + + res.Content = formattedAmount + return res, nil +} + +// GetRecipient returns the transaction recipient phone number from the gdbm. +func (h *MenuHandlers) GetRecipient(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 + recipient, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if len(recipient) == 0 { + logg.ErrorCtxf(ctx, "recipient is empty", "key", storedb.DATA_TEMPORARY_VALUE) + return res, fmt.Errorf("Data error encountered") + } + + res.Content = string(recipient) + + return res, nil +} + +// GetSender returns the sessionId (phoneNumber). +func (h *MenuHandlers) GetSender(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") + } + + res.Content = sessionId + + return res, nil +} + +// GetAmount retrieves the amount from teh Gdbm Db. +func (h *MenuHandlers) GetAmount(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 + + // retrieve the active symbol + activeSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) + return res, err + } + + amount, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_AMOUNT) + + res.Content = fmt.Sprintf("%s %s", string(amount), string(activeSym)) + + return res, nil +} + +// InitiateTransaction calls the TokenTransfer and returns a confirmation based on the result. +func (h *MenuHandlers) InitiateTransaction(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_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + data, err := store.ReadTransactionData(ctx, h.userdataStore, sessionId) + if err != nil { + return res, err + } + + finalAmountStr, err := store.ParseAndScaleAmount(data.Amount, data.ActiveDecimal) + if err != nil { + return res, err + } + + // Call TokenTransfer + r, err := h.accountService.TokenTransfer(ctx, finalAmountStr, data.PublicKey, data.Recipient, data.ActiveAddress) + if err != nil { + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + res.FlagSet = append(res.FlagSet, flag_api_error) + res.Content = l.Get("Your request failed. Please try again later.") + logg.ErrorCtxf(ctx, "failed on TokenTransfer", "error", err) + return res, nil + } + + trackingId := r.TrackingId + logg.InfoCtxf(ctx, "TokenTransfer", "trackingId", trackingId) + + res.Content = l.Get( + "Your request has been sent. %s will receive %s %s from %s.", + data.TemporaryValue, + data.Amount, + data.ActiveSym, + sessionId, + ) + + res.FlagReset = append(res.FlagReset, flag_account_authorized) + return res, nil +} -- 2.45.2 From fea0befd9fa310f16cf7be7aaa277c1f3c75441d Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 1 Jul 2025 02:04:45 +0300 Subject: [PATCH 08/26] added profile.go for profile related functionality --- handlers/application/menuhandler.go | 603 --------------------------- handlers/application/profile.go | 616 ++++++++++++++++++++++++++++ 2 files changed, 616 insertions(+), 603 deletions(-) create mode 100644 handlers/application/profile.go diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 77885e0..b184126 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -21,7 +21,6 @@ import ( "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/state" commonlang "git.grassecon.net/grassrootseconomics/common/lang" - "git.grassecon.net/grassrootseconomics/common/person" "git.grassecon.net/grassrootseconomics/common/pin" "git.grassecon.net/grassrootseconomics/sarafu-api/remote" "git.grassecon.net/grassrootseconomics/sarafu-vise/config" @@ -278,339 +277,6 @@ func (h *MenuHandlers) ResetUnregisteredNumber(ctx context.Context, sym string, return res, nil } -// SaveFirstname updates the first name in the gdbm with the provided input. -func (h *MenuHandlers) SaveFirstname(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") - } - firstName := string(input) - - store := h.userdataStore - logdb := h.logDb - - flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") - flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set") - - allowUpdate := h.st.MatchFlag(flag_allow_update, true) - firstNameSet := h.st.MatchFlag(flag_firstname_set, true) - if allowUpdate { - temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if len(temporaryFirstName) == 0 { - logg.ErrorCtxf(ctx, "temporaryFirstName is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryFirstName)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write firstName entry with", "key", storedb.DATA_FIRST_NAME, "value", temporaryFirstName, "error", err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_firstname_set) - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryFirstName)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FIRST_NAME, "value", temporaryFirstName) - } - } else { - if firstNameSet { - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", firstName, "error", err) - return res, err - } - } else { - h.profile.InsertOrShift(0, firstName) - } - } - - return res, nil -} - -// SaveFamilyname updates the family name in the gdbm with the provided input. -func (h *MenuHandlers) SaveFamilyname(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") - } - - store := h.userdataStore - logdb := h.logDb - familyName := string(input) - - flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") - flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set") - allowUpdate := h.st.MatchFlag(flag_allow_update, true) - familyNameSet := h.st.MatchFlag(flag_familyname_set, true) - - if allowUpdate { - temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if len(temporaryFamilyName) == 0 { - logg.ErrorCtxf(ctx, "temporaryFamilyName is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME, []byte(temporaryFamilyName)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write familyName entry with", "key", storedb.DATA_FAMILY_NAME, "value", temporaryFamilyName, "error", err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_familyname_set) - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME, []byte(temporaryFamilyName)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FAMILY_NAME, "value", temporaryFamilyName) - } - } else { - if familyNameSet { - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", familyName, "error", err) - return res, err - } - } else { - h.profile.InsertOrShift(1, familyName) - } - } - - return res, nil -} - -// VerifyYob verifies the length of the given input. -func (h *MenuHandlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - var err error - - flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") - date := string(input) - _, err = strconv.Atoi(date) - if err != nil { - // If conversion fails, input is not numeric - res.FlagSet = append(res.FlagSet, flag_incorrect_date_format) - return res, nil - } - - if person.IsValidYOb(date) { - res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) - } else { - res.FlagSet = append(res.FlagSet, flag_incorrect_date_format) - } - return res, nil -} - -// ResetIncorrectYob resets the incorrect date format flag after a new attempt. -func (h *MenuHandlers) ResetIncorrectYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - - flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") - res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) - return res, nil -} - -// SaveYOB updates the Year of Birth(YOB) in the gdbm with the provided input. -func (h *MenuHandlers) SaveYob(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") - } - yob := string(input) - store := h.userdataStore - logdb := h.logDb - - flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") - flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set") - - allowUpdate := h.st.MatchFlag(flag_allow_update, true) - yobSet := h.st.MatchFlag(flag_yob_set, true) - - if allowUpdate { - temporaryYob, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if len(temporaryYob) == 0 { - logg.ErrorCtxf(ctx, "temporaryYob is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_YOB, []byte(temporaryYob)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write yob entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryYob, "error", err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_yob_set) - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_YOB, []byte(temporaryYob)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write yob db log entry", "key", storedb.DATA_YOB, "value", temporaryYob) - } - } else { - if yobSet { - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", yob, "error", err) - return res, err - } - } else { - h.profile.InsertOrShift(3, yob) - } - } - - return res, nil -} - -// SaveLocation updates the location in the gdbm with the provided input. -func (h *MenuHandlers) SaveLocation(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") - } - location := string(input) - store := h.userdataStore - logdb := h.logDb - - flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") - flag_location_set, _ := h.flagManager.GetFlag("flag_location_set") - allowUpdate := h.st.MatchFlag(flag_allow_update, true) - locationSet := h.st.MatchFlag(flag_location_set, true) - - if allowUpdate { - temporaryLocation, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if len(temporaryLocation) == 0 { - logg.ErrorCtxf(ctx, "temporaryLocation is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_LOCATION, []byte(temporaryLocation)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write location entry with", "key", storedb.DATA_LOCATION, "value", temporaryLocation, "error", err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_location_set) - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_LOCATION, []byte(temporaryLocation)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write location db log entry", "key", storedb.DATA_LOCATION, "value", temporaryLocation) - } - } else { - if locationSet { - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", location, "error", err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_location_set) - } else { - h.profile.InsertOrShift(4, location) - } - } - - return res, nil -} - -// SaveGender updates the gender in the gdbm with the provided input. -func (h *MenuHandlers) SaveGender(ctx context.Context, sym string, input []byte) (resource.Result, error) { - symbol, _ := h.st.Where() - var res resource.Result - var err error - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - gender := strings.Split(symbol, "_")[1] - store := h.userdataStore - logdb := h.logDb - flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") - flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set") - - allowUpdate := h.st.MatchFlag(flag_allow_update, true) - genderSet := h.st.MatchFlag(flag_gender_set, true) - - if allowUpdate { - temporaryGender, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if len(temporaryGender) == 0 { - logg.ErrorCtxf(ctx, "temporaryGender is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_GENDER, []byte(temporaryGender)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write gender entry with", "key", storedb.DATA_GENDER, "value", gender, "error", err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_gender_set) - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_GENDER, []byte(temporaryGender)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write gender db log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryGender) - } - - } else { - if genderSet { - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(gender)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", gender, "error", err) - return res, err - } - } else { - h.profile.InsertOrShift(2, gender) - } - } - - return res, nil -} - -// SaveOfferings updates the offerings(goods and services provided by the user) in the gdbm with the provided input. -func (h *MenuHandlers) SaveOfferings(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") - } - - offerings := string(input) - store := h.userdataStore - logdb := h.logDb - - flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") - flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set") - - allowUpdate := h.st.MatchFlag(flag_allow_update, true) - offeringsSet := h.st.MatchFlag(flag_offerings_set, true) - - if allowUpdate { - temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if len(temporaryOfferings) == 0 { - logg.ErrorCtxf(ctx, "temporaryOfferings is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_OFFERINGS, []byte(temporaryOfferings)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write offerings entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", offerings, "error", err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_offerings_set) - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryOfferings)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write offerings db log entry", "key", storedb.DATA_OFFERINGS, "value", offerings) - } - } else { - if offeringsSet { - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", offerings, "error", err) - return res, err - } - } else { - h.profile.InsertOrShift(5, offerings) - } - } - - return res, nil -} - // ResetAllowUpdate resets the allowupdate flag that allows a user to update profile data. func (h *MenuHandlers) ResetAllowUpdate(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -619,275 +285,6 @@ func (h *MenuHandlers) ResetAllowUpdate(ctx context.Context, sym string, input [ return res, nil } -// GetCurrentProfileInfo retrieves specific profile fields based on the current state of the USSD session. -// Uses flag management system to track profile field status and handle menu navigation. -func (h *MenuHandlers) GetCurrentProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - var profileInfo []byte - var defaultValue string - var err error - - flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set") - flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set") - flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set") - flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set") - flag_location_set, _ := h.flagManager.GetFlag("flag_location_set") - flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set") - flag_back_set, _ := h.flagManager.GetFlag("flag_back_set") - - res.FlagReset = append(res.FlagReset, flag_back_set) - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - language, ok := ctx.Value("Language").(lang.Language) - if !ok { - return res, fmt.Errorf("value for 'Language' is not of type lang.Language") - } - code := language.Code - if code == "swa" { - defaultValue = "Haipo" - } else { - defaultValue = "Not Provided" - } - - sm, _ := h.st.Where() - parts := strings.SplitN(sm, "_", 2) - filename := parts[1] - dbKeyStr := "DATA_" + strings.ToUpper(filename) - logg.InfoCtxf(ctx, "GetCurrentProfileInfo", "filename", filename, "dbKeyStr:", dbKeyStr) - dbKey, err := storedb.StringToDataTyp(dbKeyStr) - - if err != nil { - return res, err - } - store := h.userdataStore - - switch dbKey { - case storedb.DATA_FIRST_NAME: - profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME) - if err != nil { - if db.IsNotFound(err) { - res.Content = defaultValue - break - } - logg.ErrorCtxf(ctx, "Failed to read first name entry with", "key", "error", storedb.DATA_FIRST_NAME, err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_firstname_set) - res.Content = string(profileInfo) - case storedb.DATA_FAMILY_NAME: - profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME) - if err != nil { - if db.IsNotFound(err) { - res.Content = defaultValue - break - } - logg.ErrorCtxf(ctx, "Failed to read family name entry with", "key", "error", storedb.DATA_FAMILY_NAME, err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_familyname_set) - res.Content = string(profileInfo) - - case storedb.DATA_GENDER: - profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_GENDER) - if err != nil { - if db.IsNotFound(err) { - res.Content = defaultValue - break - } - logg.ErrorCtxf(ctx, "Failed to read gender entry with", "key", "error", storedb.DATA_GENDER, err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_gender_set) - res.Content = string(profileInfo) - case storedb.DATA_YOB: - profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_YOB) - if err != nil { - if db.IsNotFound(err) { - res.Content = defaultValue - break - } - logg.ErrorCtxf(ctx, "Failed to read year of birth(yob) entry with", "key", "error", storedb.DATA_YOB, err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_yob_set) - res.Content = string(profileInfo) - case storedb.DATA_LOCATION: - profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION) - if err != nil { - if db.IsNotFound(err) { - res.Content = defaultValue - break - } - logg.ErrorCtxf(ctx, "Failed to read location entry with", "key", "error", storedb.DATA_LOCATION, err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_location_set) - res.Content = string(profileInfo) - case storedb.DATA_OFFERINGS: - profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS) - if err != nil { - if db.IsNotFound(err) { - res.Content = defaultValue - break - } - logg.ErrorCtxf(ctx, "Failed to read offerings entry with", "key", "error", storedb.DATA_OFFERINGS, err) - return res, err - } - res.FlagSet = append(res.FlagSet, flag_offerings_set) - res.Content = string(profileInfo) - case storedb.DATA_ACCOUNT_ALIAS: - profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS) - if err != nil { - if db.IsNotFound(err) { - res.Content = defaultValue - break - } - logg.ErrorCtxf(ctx, "Failed to read account alias entry with", "key", "error", storedb.DATA_ACCOUNT_ALIAS, err) - return res, err - } - alias := string(profileInfo) - if alias == "" { - res.Content = defaultValue - } else { - res.Content = alias - } - default: - break - } - - return res, nil -} - -// GetProfileInfo provides a comprehensive view of a user's profile. -func (h *MenuHandlers) GetProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - var defaultValue string - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - language, ok := ctx.Value("Language").(lang.Language) - if !ok { - return res, fmt.Errorf("value for 'Language' is not of type lang.Language") - } - code := language.Code - if code == "swa" { - defaultValue = "Haipo" - } else { - defaultValue = "Not Provided" - } - - // Helper function to handle nil byte slices and convert them to string - getEntryOrDefault := func(entry []byte, err error) string { - if err != nil || entry == nil { - return defaultValue - } - return string(entry) - } - store := h.userdataStore - // Retrieve user data as strings with fallback to defaultValue - firstName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME)) - familyName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME)) - yob := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_YOB)) - gender := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_GENDER)) - location := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION)) - offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS)) - alias := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS)) - - if alias != defaultValue && alias != "" { - alias = strings.Split(alias, ".")[0] - } else { - alias = defaultValue - } - - // Construct the full name - name := person.ConstructName(firstName, familyName, defaultValue) - - // Calculate age from year of birth - age := defaultValue - if yob != defaultValue { - if yobInt, err := strconv.Atoi(yob); err == nil { - age = strconv.Itoa(person.CalculateAgeWithYOB(yobInt)) - } else { - return res, fmt.Errorf("invalid year of birth: %v", err) - } - } - switch language.Code { - case "eng": - res.Content = fmt.Sprintf( - "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n", - name, gender, age, location, offerings, alias, - ) - case "swa": - res.Content = fmt.Sprintf( - "Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\nLakabu yako: %s\n", - name, gender, age, location, offerings, alias, - ) - default: - res.Content = fmt.Sprintf( - "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n", - name, gender, age, location, offerings, alias, - ) - } - - return res, nil -} - -// handles bulk updates of profile information. -func (h *MenuHandlers) insertProfileItems(ctx context.Context, sessionId string, res *resource.Result) error { - var err error - userStore := h.userdataStore - profileFlagNames := []string{ - "flag_firstname_set", - "flag_familyname_set", - "flag_yob_set", - "flag_gender_set", - "flag_location_set", - "flag_offerings_set", - } - profileDataKeys := []storedb.DataTyp{ - storedb.DATA_FIRST_NAME, - storedb.DATA_FAMILY_NAME, - storedb.DATA_GENDER, - storedb.DATA_YOB, - storedb.DATA_LOCATION, - storedb.DATA_OFFERINGS, - } - for index, profileItem := range h.profile.ProfileItems { - // Ensure the profileItem is not "0"(is set) - if profileItem != "0" { - flag, _ := h.flagManager.GetFlag(profileFlagNames[index]) - isProfileItemSet := h.st.MatchFlag(flag, true) - if !isProfileItemSet { - err = userStore.WriteEntry(ctx, sessionId, profileDataKeys[index], []byte(profileItem)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write profile entry with", "key", profileDataKeys[index], "value", profileItem, "error", err) - return err - } - res.FlagSet = append(res.FlagSet, flag) - } - } - } - return nil -} - -// UpdateAllProfileItems is used to persist all the new profile information and setup the required profile flags. -func (h *MenuHandlers) UpdateAllProfileItems(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") - } - err := h.insertProfileItems(ctx, sessionId, &res) - if err != nil { - return res, err - } - return res, nil -} - // ResetAccountAuthorized resets the account authorization flag after a successful PIN entry. func (h *MenuHandlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result diff --git a/handlers/application/profile.go b/handlers/application/profile.go new file mode 100644 index 0000000..62b264d --- /dev/null +++ b/handlers/application/profile.go @@ -0,0 +1,616 @@ +package application + +import ( + "context" + "fmt" + "strconv" + "strings" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/lang" + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/common/person" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" +) + +// SaveFirstname updates the first name in the gdbm with the provided input. +func (h *MenuHandlers) SaveFirstname(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") + } + firstName := string(input) + + store := h.userdataStore + logdb := h.logDb + + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set") + + allowUpdate := h.st.MatchFlag(flag_allow_update, true) + firstNameSet := h.st.MatchFlag(flag_firstname_set, true) + if allowUpdate { + temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if len(temporaryFirstName) == 0 { + logg.ErrorCtxf(ctx, "temporaryFirstName is empty", "key", storedb.DATA_TEMPORARY_VALUE) + return res, fmt.Errorf("Data error encountered") + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryFirstName)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write firstName entry with", "key", storedb.DATA_FIRST_NAME, "value", temporaryFirstName, "error", err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_firstname_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryFirstName)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FIRST_NAME, "value", temporaryFirstName) + } + } else { + if firstNameSet { + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", firstName, "error", err) + return res, err + } + } else { + h.profile.InsertOrShift(0, firstName) + } + } + + return res, nil +} + +// SaveFamilyname updates the family name in the gdbm with the provided input. +func (h *MenuHandlers) SaveFamilyname(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") + } + + store := h.userdataStore + logdb := h.logDb + familyName := string(input) + + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set") + allowUpdate := h.st.MatchFlag(flag_allow_update, true) + familyNameSet := h.st.MatchFlag(flag_familyname_set, true) + + if allowUpdate { + temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if len(temporaryFamilyName) == 0 { + logg.ErrorCtxf(ctx, "temporaryFamilyName is empty", "key", storedb.DATA_TEMPORARY_VALUE) + return res, fmt.Errorf("Data error encountered") + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME, []byte(temporaryFamilyName)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write familyName entry with", "key", storedb.DATA_FAMILY_NAME, "value", temporaryFamilyName, "error", err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_familyname_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME, []byte(temporaryFamilyName)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FAMILY_NAME, "value", temporaryFamilyName) + } + } else { + if familyNameSet { + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", familyName, "error", err) + return res, err + } + } else { + h.profile.InsertOrShift(1, familyName) + } + } + + return res, nil +} + +// VerifyYob verifies the length of the given input. +func (h *MenuHandlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + var err error + + flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") + date := string(input) + _, err = strconv.Atoi(date) + if err != nil { + // If conversion fails, input is not numeric + res.FlagSet = append(res.FlagSet, flag_incorrect_date_format) + return res, nil + } + + if person.IsValidYOb(date) { + res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) + } else { + res.FlagSet = append(res.FlagSet, flag_incorrect_date_format) + } + return res, nil +} + +// ResetIncorrectYob resets the incorrect date format flag after a new attempt. +func (h *MenuHandlers) ResetIncorrectYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") + res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) + return res, nil +} + +// SaveYOB updates the Year of Birth(YOB) in the gdbm with the provided input. +func (h *MenuHandlers) SaveYob(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") + } + yob := string(input) + store := h.userdataStore + logdb := h.logDb + + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set") + + allowUpdate := h.st.MatchFlag(flag_allow_update, true) + yobSet := h.st.MatchFlag(flag_yob_set, true) + + if allowUpdate { + temporaryYob, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if len(temporaryYob) == 0 { + logg.ErrorCtxf(ctx, "temporaryYob is empty", "key", storedb.DATA_TEMPORARY_VALUE) + return res, fmt.Errorf("Data error encountered") + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_YOB, []byte(temporaryYob)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write yob entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryYob, "error", err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_yob_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_YOB, []byte(temporaryYob)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write yob db log entry", "key", storedb.DATA_YOB, "value", temporaryYob) + } + } else { + if yobSet { + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", yob, "error", err) + return res, err + } + } else { + h.profile.InsertOrShift(3, yob) + } + } + + return res, nil +} + +// SaveLocation updates the location in the gdbm with the provided input. +func (h *MenuHandlers) SaveLocation(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") + } + location := string(input) + store := h.userdataStore + logdb := h.logDb + + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + flag_location_set, _ := h.flagManager.GetFlag("flag_location_set") + allowUpdate := h.st.MatchFlag(flag_allow_update, true) + locationSet := h.st.MatchFlag(flag_location_set, true) + + if allowUpdate { + temporaryLocation, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if len(temporaryLocation) == 0 { + logg.ErrorCtxf(ctx, "temporaryLocation is empty", "key", storedb.DATA_TEMPORARY_VALUE) + return res, fmt.Errorf("Data error encountered") + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_LOCATION, []byte(temporaryLocation)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write location entry with", "key", storedb.DATA_LOCATION, "value", temporaryLocation, "error", err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_location_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_LOCATION, []byte(temporaryLocation)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write location db log entry", "key", storedb.DATA_LOCATION, "value", temporaryLocation) + } + } else { + if locationSet { + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", location, "error", err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_location_set) + } else { + h.profile.InsertOrShift(4, location) + } + } + + return res, nil +} + +// SaveGender updates the gender in the gdbm with the provided input. +func (h *MenuHandlers) SaveGender(ctx context.Context, sym string, input []byte) (resource.Result, error) { + symbol, _ := h.st.Where() + var res resource.Result + var err error + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + gender := strings.Split(symbol, "_")[1] + store := h.userdataStore + logdb := h.logDb + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set") + + allowUpdate := h.st.MatchFlag(flag_allow_update, true) + genderSet := h.st.MatchFlag(flag_gender_set, true) + + if allowUpdate { + temporaryGender, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if len(temporaryGender) == 0 { + logg.ErrorCtxf(ctx, "temporaryGender is empty", "key", storedb.DATA_TEMPORARY_VALUE) + return res, fmt.Errorf("Data error encountered") + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_GENDER, []byte(temporaryGender)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write gender entry with", "key", storedb.DATA_GENDER, "value", gender, "error", err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_gender_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_GENDER, []byte(temporaryGender)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write gender db log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryGender) + } + + } else { + if genderSet { + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(gender)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", gender, "error", err) + return res, err + } + } else { + h.profile.InsertOrShift(2, gender) + } + } + + return res, nil +} + +// SaveOfferings updates the offerings(goods and services provided by the user) in the gdbm with the provided input. +func (h *MenuHandlers) SaveOfferings(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") + } + + offerings := string(input) + store := h.userdataStore + logdb := h.logDb + + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set") + + allowUpdate := h.st.MatchFlag(flag_allow_update, true) + offeringsSet := h.st.MatchFlag(flag_offerings_set, true) + + if allowUpdate { + temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if len(temporaryOfferings) == 0 { + logg.ErrorCtxf(ctx, "temporaryOfferings is empty", "key", storedb.DATA_TEMPORARY_VALUE) + return res, fmt.Errorf("Data error encountered") + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_OFFERINGS, []byte(temporaryOfferings)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write offerings entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", offerings, "error", err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_offerings_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryOfferings)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write offerings db log entry", "key", storedb.DATA_OFFERINGS, "value", offerings) + } + } else { + if offeringsSet { + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", offerings, "error", err) + return res, err + } + } else { + h.profile.InsertOrShift(5, offerings) + } + } + + return res, nil +} + +// GetCurrentProfileInfo retrieves specific profile fields based on the current state of the USSD session. +// Uses flag management system to track profile field status and handle menu navigation. +func (h *MenuHandlers) GetCurrentProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + var profileInfo []byte + var defaultValue string + var err error + + flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set") + flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set") + flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set") + flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set") + flag_location_set, _ := h.flagManager.GetFlag("flag_location_set") + flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set") + flag_back_set, _ := h.flagManager.GetFlag("flag_back_set") + + res.FlagReset = append(res.FlagReset, flag_back_set) + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + language, ok := ctx.Value("Language").(lang.Language) + if !ok { + return res, fmt.Errorf("value for 'Language' is not of type lang.Language") + } + code := language.Code + if code == "swa" { + defaultValue = "Haipo" + } else { + defaultValue = "Not Provided" + } + + sm, _ := h.st.Where() + parts := strings.SplitN(sm, "_", 2) + filename := parts[1] + dbKeyStr := "DATA_" + strings.ToUpper(filename) + logg.InfoCtxf(ctx, "GetCurrentProfileInfo", "filename", filename, "dbKeyStr:", dbKeyStr) + dbKey, err := storedb.StringToDataTyp(dbKeyStr) + + if err != nil { + return res, err + } + store := h.userdataStore + + switch dbKey { + case storedb.DATA_FIRST_NAME: + profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME) + if err != nil { + if db.IsNotFound(err) { + res.Content = defaultValue + break + } + logg.ErrorCtxf(ctx, "Failed to read first name entry with", "key", "error", storedb.DATA_FIRST_NAME, err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_firstname_set) + res.Content = string(profileInfo) + case storedb.DATA_FAMILY_NAME: + profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME) + if err != nil { + if db.IsNotFound(err) { + res.Content = defaultValue + break + } + logg.ErrorCtxf(ctx, "Failed to read family name entry with", "key", "error", storedb.DATA_FAMILY_NAME, err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_familyname_set) + res.Content = string(profileInfo) + + case storedb.DATA_GENDER: + profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_GENDER) + if err != nil { + if db.IsNotFound(err) { + res.Content = defaultValue + break + } + logg.ErrorCtxf(ctx, "Failed to read gender entry with", "key", "error", storedb.DATA_GENDER, err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_gender_set) + res.Content = string(profileInfo) + case storedb.DATA_YOB: + profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_YOB) + if err != nil { + if db.IsNotFound(err) { + res.Content = defaultValue + break + } + logg.ErrorCtxf(ctx, "Failed to read year of birth(yob) entry with", "key", "error", storedb.DATA_YOB, err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_yob_set) + res.Content = string(profileInfo) + case storedb.DATA_LOCATION: + profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION) + if err != nil { + if db.IsNotFound(err) { + res.Content = defaultValue + break + } + logg.ErrorCtxf(ctx, "Failed to read location entry with", "key", "error", storedb.DATA_LOCATION, err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_location_set) + res.Content = string(profileInfo) + case storedb.DATA_OFFERINGS: + profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS) + if err != nil { + if db.IsNotFound(err) { + res.Content = defaultValue + break + } + logg.ErrorCtxf(ctx, "Failed to read offerings entry with", "key", "error", storedb.DATA_OFFERINGS, err) + return res, err + } + res.FlagSet = append(res.FlagSet, flag_offerings_set) + res.Content = string(profileInfo) + case storedb.DATA_ACCOUNT_ALIAS: + profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS) + if err != nil { + if db.IsNotFound(err) { + res.Content = defaultValue + break + } + logg.ErrorCtxf(ctx, "Failed to read account alias entry with", "key", "error", storedb.DATA_ACCOUNT_ALIAS, err) + return res, err + } + alias := string(profileInfo) + if alias == "" { + res.Content = defaultValue + } else { + res.Content = alias + } + default: + break + } + + return res, nil +} + +// GetProfileInfo provides a comprehensive view of a user's profile. +func (h *MenuHandlers) GetProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + var defaultValue string + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + language, ok := ctx.Value("Language").(lang.Language) + if !ok { + return res, fmt.Errorf("value for 'Language' is not of type lang.Language") + } + code := language.Code + if code == "swa" { + defaultValue = "Haipo" + } else { + defaultValue = "Not Provided" + } + + // Helper function to handle nil byte slices and convert them to string + getEntryOrDefault := func(entry []byte, err error) string { + if err != nil || entry == nil { + return defaultValue + } + return string(entry) + } + store := h.userdataStore + // Retrieve user data as strings with fallback to defaultValue + firstName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME)) + familyName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME)) + yob := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_YOB)) + gender := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_GENDER)) + location := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION)) + offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS)) + alias := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS)) + + if alias != defaultValue && alias != "" { + alias = strings.Split(alias, ".")[0] + } else { + alias = defaultValue + } + + // Construct the full name + name := person.ConstructName(firstName, familyName, defaultValue) + + // Calculate age from year of birth + age := defaultValue + if yob != defaultValue { + if yobInt, err := strconv.Atoi(yob); err == nil { + age = strconv.Itoa(person.CalculateAgeWithYOB(yobInt)) + } else { + return res, fmt.Errorf("invalid year of birth: %v", err) + } + } + switch language.Code { + case "eng": + res.Content = fmt.Sprintf( + "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n", + name, gender, age, location, offerings, alias, + ) + case "swa": + res.Content = fmt.Sprintf( + "Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\nLakabu yako: %s\n", + name, gender, age, location, offerings, alias, + ) + default: + res.Content = fmt.Sprintf( + "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n", + name, gender, age, location, offerings, alias, + ) + } + + return res, nil +} + +// handles bulk updates of profile information. +func (h *MenuHandlers) insertProfileItems(ctx context.Context, sessionId string, res *resource.Result) error { + var err error + userStore := h.userdataStore + profileFlagNames := []string{ + "flag_firstname_set", + "flag_familyname_set", + "flag_yob_set", + "flag_gender_set", + "flag_location_set", + "flag_offerings_set", + } + profileDataKeys := []storedb.DataTyp{ + storedb.DATA_FIRST_NAME, + storedb.DATA_FAMILY_NAME, + storedb.DATA_GENDER, + storedb.DATA_YOB, + storedb.DATA_LOCATION, + storedb.DATA_OFFERINGS, + } + for index, profileItem := range h.profile.ProfileItems { + // Ensure the profileItem is not "0"(is set) + if profileItem != "0" { + flag, _ := h.flagManager.GetFlag(profileFlagNames[index]) + isProfileItemSet := h.st.MatchFlag(flag, true) + if !isProfileItemSet { + err = userStore.WriteEntry(ctx, sessionId, profileDataKeys[index], []byte(profileItem)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write profile entry with", "key", profileDataKeys[index], "value", profileItem, "error", err) + return err + } + res.FlagSet = append(res.FlagSet, flag) + } + } + } + return nil +} + +// UpdateAllProfileItems is used to persist all the new profile information and setup the required profile flags. +func (h *MenuHandlers) UpdateAllProfileItems(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") + } + err := h.insertProfileItems(ctx, sessionId, &res) + if err != nil { + return res, err + } + return res, nil +} -- 2.45.2 From e6b97b92566fc29874dc17c571576ba0183ded8f Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 16:19:18 +0300 Subject: [PATCH 09/26] update the voucher functions to match master --- handlers/application/vouchers.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/handlers/application/vouchers.go b/handlers/application/vouchers.go index 5db14f3..72bb6b2 100644 --- a/handlers/application/vouchers.go +++ b/handlers/application/vouchers.go @@ -3,6 +3,7 @@ package application import ( "context" "fmt" + "strings" "git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/resource" @@ -101,8 +102,9 @@ func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []b } if activeData == nil { - logg.ErrorCtxf(ctx, "activeSym not found in vouchers", "activeSym", activeSymStr) - return res, fmt.Errorf("activeSym %s not found in vouchers", activeSymStr) + logg.ErrorCtxf(ctx, "activeSym not found in vouchers, setting the first voucher as the default", "activeSym", activeSymStr) + firstVoucher := vouchersResp[0] + activeData = &firstVoucher } // Scale down the balance @@ -150,17 +152,25 @@ func (h *MenuHandlers) GetVoucherList(ctx context.Context, sym string, input []b // Read vouchers from the store voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS) - logg.InfoCtxf(ctx, "reading GetVoucherList entries for sessionId: %s", sessionId, "key", storedb.DATA_VOUCHER_SYMBOLS, "voucherData", voucherData) + logg.InfoCtxf(ctx, "reading voucherData in GetVoucherList", "sessionId", sessionId, "key", storedb.DATA_VOUCHER_SYMBOLS, "voucherData", voucherData) if err != nil { logg.ErrorCtxf(ctx, "failed to read voucherData entires with", "key", storedb.DATA_VOUCHER_SYMBOLS, "error", err) return res, err } - formattedData := h.ReplaceSeparatorFunc(string(voucherData)) + voucherBalances, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_BALANCES) + logg.InfoCtxf(ctx, "reading voucherBalances in GetVoucherList", "sessionId", sessionId, "key", storedb.DATA_VOUCHER_BALANCES, "voucherBalances", voucherBalances) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read voucherData entires with", "key", storedb.DATA_VOUCHER_BALANCES, "error", err) + return res, err + } - logg.InfoCtxf(ctx, "final output for sessionId: %s", sessionId, "key", storedb.DATA_VOUCHER_SYMBOLS, "formattedData", formattedData) + formattedVoucherList := store.FormatVoucherList(ctx, string(voucherData), string(voucherBalances)) + finalOutput := strings.Join(formattedVoucherList, "\n") - res.Content = string(formattedData) + logg.InfoCtxf(ctx, "final output for GetVoucherList", "sessionId", sessionId, "finalOutput", finalOutput) + + res.Content = finalOutput return res, nil } -- 2.45.2 From 6c962baa816e51aba201f344389aa98a01277613 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 16:20:59 +0300 Subject: [PATCH 10/26] added pools.go for pool related functions --- handlers/application/menuhandler.go | 775 ---------------------------- handlers/application/pools.go | 163 ++++++ handlers/application/poolswap.go | 46 -- 3 files changed, 163 insertions(+), 821 deletions(-) create mode 100644 handlers/application/pools.go diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index b953ce3..110595f 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -22,7 +22,6 @@ import ( commonlang "git.grassecon.net/grassrootseconomics/common/lang" "git.grassecon.net/grassrootseconomics/common/pin" "git.grassecon.net/grassrootseconomics/sarafu-api/remote" - "git.grassecon.net/grassrootseconomics/sarafu-vise/config" "git.grassecon.net/grassrootseconomics/sarafu-vise/internal/sms" "git.grassecon.net/grassrootseconomics/sarafu-vise/profile" "git.grassecon.net/grassrootseconomics/sarafu-vise/store" @@ -558,780 +557,6 @@ func (h *MenuHandlers) FetchCommunityBalance(ctx context.Context, sym string, in return res, nil } -// ValidateRecipient validates that the given input is valid. -// -// TODO: split up functino -func (h *MenuHandlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - var AliasAddressResult string - var AliasAddress *models.AliasAddress - store := h.userdataStore - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") - flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - - // remove white spaces - recipient := strings.ReplaceAll(string(input), " ", "") - - if recipient != "0" { - recipientType, err := identity.CheckRecipient(recipient) - if err != nil { - // Invalid recipient format (not a phone number, address, or valid alias format) - res.FlagSet = append(res.FlagSet, flag_invalid_recipient) - res.Content = recipient - - return res, nil - } - - // save the recipient as the temporaryRecipient - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(recipient)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryRecipient entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", recipient, "error", err) - return res, err - } - - switch recipientType { - case "phone number": - // format the phone number - formattedNumber, err := phone.FormatPhoneNumber(recipient) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", recipient, "error", err) - return res, err - } - - // Check if the phone number is registered - publicKey, err := store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY) - if err != nil { - if db.IsNotFound(err) { - logg.InfoCtxf(ctx, "Unregistered phone number: %s", recipient) - res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite) - res.Content = recipient - return res, nil - } - - logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } - - // Save the publicKey as the recipient - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, publicKey) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", string(publicKey), "error", err) - return res, err - } - - case "address": - // checksum the address - address := ethutils.ChecksumAddress(recipient) - - // Save the valid Ethereum address as the recipient - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(address)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", recipient, "error", err) - return res, err - } - - case "alias": - if strings.Contains(recipient, ".") { - AliasAddress, err = h.accountService.CheckAliasAddress(ctx, recipient) - if err == nil { - AliasAddressResult = AliasAddress.Address - } else { - logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err) - } - } else { - //Perform a search for each search domain,break on first match - for _, domain := range config.SearchDomains() { - fqdn := fmt.Sprintf("%s.%s", recipient, domain) - logg.InfoCtxf(ctx, "Resolving with fqdn alias", "alias", fqdn) - AliasAddress, err = h.accountService.CheckAliasAddress(ctx, fqdn) - if err == nil { - res.FlagReset = append(res.FlagReset, flag_api_error) - AliasAddressResult = AliasAddress.Address - continue - } else { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err) - return res, nil - } - } - } - if AliasAddressResult == "" { - res.Content = recipient - res.FlagSet = append(res.FlagSet, flag_invalid_recipient) - return res, nil - } else { - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(AliasAddressResult)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", AliasAddressResult, "error", err) - return res, err - } - } - } - } - - return res, nil -} - -// TransactionReset resets the previous transaction data (Recipient and Amount) -// as well as the invalid flags. -func (h *MenuHandlers) TransactionReset(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_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") - flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") - store := h.userdataStore - err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte("")) - if err != nil { - return res, nil - } - - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte("")) - if err != nil { - return res, nil - } - - res.FlagReset = append(res.FlagReset, flag_invalid_recipient, flag_invalid_recipient_with_invite) - - return res, nil -} - -// InviteValidRecipient sends an invitation to the valid phone number. -func (h *MenuHandlers) InviteValidRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - store := h.userdataStore - smsservice := h.smsService - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - code := codeFromCtx(ctx) - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - - recipient, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to read invalid recipient info", "error", err) - return res, err - } - - if !phone.IsValidPhoneNumber(string(recipient)) { - logg.InfoCtxf(ctx, "corrupted recipient", "key", storedb.DATA_TEMPORARY_VALUE, "recipient", recipient) - return res, nil - } - - _, err = smsservice.Accountservice.SendUpsellSMS(ctx, sessionId, string(recipient)) - if err != nil { - res.Content = l.Get("Your invite request for %s to Sarafu Network failed. Please try again later.", string(recipient)) - return res, nil - } - res.Content = l.Get("Your invitation to %s to join Sarafu Network has been sent.", string(recipient)) - return res, nil -} - -// ResetTransactionAmount resets the transaction amount and invalid flag. -func (h *MenuHandlers) ResetTransactionAmount(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") - store := h.userdataStore - err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte("")) - if err != nil { - return res, nil - } - - res.FlagReset = append(res.FlagReset, flag_invalid_amount) - - return res, nil -} - -// MaxAmount gets the current balance from the API and sets it as -// the result content. -func (h *MenuHandlers) MaxAmount(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") - } - store := h.userdataStore - - activeBal, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err) - return res, err - } - - res.Content = string(activeBal) - - return res, nil -} - -// ValidateAmount ensures that the given input is a valid amount and that -// it is not more than the current balance. -func (h *MenuHandlers) ValidateAmount(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") - } - flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") - userStore := h.userdataStore - - var balanceValue float64 - - // retrieve the active balance - activeBal, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err) - return res, err - } - balanceValue, err = strconv.ParseFloat(string(activeBal), 64) - if err != nil { - logg.ErrorCtxf(ctx, "Failed to convert the activeBal to a float", "error", err) - return res, err - } - - // 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 - return res, nil - } - - if inputAmount > balanceValue { - res.FlagSet = append(res.FlagSet, flag_invalid_amount) - res.Content = amountStr - return res, nil - } - - // Format the amount to 2 decimal places before saving (truncated) - formattedAmount, err := store.TruncateDecimalString(amountStr, 2) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_invalid_amount) - res.Content = amountStr - return res, nil - } - - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(formattedAmount)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write amount entry with", "key", storedb.DATA_AMOUNT, "value", formattedAmount, "error", err) - return res, err - } - - res.Content = formattedAmount - return res, nil -} - -// GetRecipient returns the transaction recipient phone number from the gdbm. -func (h *MenuHandlers) GetRecipient(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 - recipient, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if len(recipient) == 0 { - logg.ErrorCtxf(ctx, "recipient is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - - res.Content = string(recipient) - - return res, nil -} - -// RetrieveBlockedNumber gets the current number during the pin reset for other's is in progress. -func (h *MenuHandlers) RetrieveBlockedNumber(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 - blockedNumber, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) - - res.Content = string(blockedNumber) - - return res, nil -} - -// GetSender returns the sessionId (phoneNumber). -func (h *MenuHandlers) GetSender(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") - } - - res.Content = sessionId - - return res, nil -} - -// GetAmount retrieves the amount from teh Gdbm Db. -func (h *MenuHandlers) GetAmount(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 - - // retrieve the active symbol - activeSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) - return res, err - } - - amount, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_AMOUNT) - - res.Content = fmt.Sprintf("%s %s", string(amount), string(activeSym)) - - return res, nil -} - -// InitiateTransaction calls the TokenTransfer and returns a confirmation based on the result. -func (h *MenuHandlers) InitiateTransaction(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_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") - - code := codeFromCtx(ctx) - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - - data, err := store.ReadTransactionData(ctx, h.userdataStore, sessionId) - if err != nil { - return res, err - } - - finalAmountStr, err := store.ParseAndScaleAmount(data.Amount, data.ActiveDecimal) - if err != nil { - return res, err - } - - // Call TokenTransfer - r, err := h.accountService.TokenTransfer(ctx, finalAmountStr, data.PublicKey, data.Recipient, data.ActiveAddress) - if err != nil { - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - res.FlagSet = append(res.FlagSet, flag_api_error) - res.Content = l.Get("Your request failed. Please try again later.") - logg.ErrorCtxf(ctx, "failed on TokenTransfer", "error", err) - return res, nil - } - - trackingId := r.TrackingId - logg.InfoCtxf(ctx, "TokenTransfer", "trackingId", trackingId) - - res.Content = l.Get( - "Your request has been sent. %s will receive %s %s from %s.", - data.TemporaryValue, - data.Amount, - data.ActiveSym, - sessionId, - ) - - res.FlagReset = append(res.FlagReset, flag_account_authorized) - return res, nil -} - -// ManageVouchers retrieves the token holdings from the API using the "PublicKey" and -// 1. sets the first as the default voucher if no active voucher is set. -// 2. Stores list of vouchers -// 3. updates the balance of the active voucher -func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - userStore := h.userdataStore - logdb := h.logDb - - 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") - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - - publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read publicKey entry", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } - - // Fetch vouchers from API - vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey)) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - return res, nil - } - res.FlagReset = append(res.FlagReset, flag_api_error) - - if len(vouchersResp) == 0 { - res.FlagSet = append(res.FlagSet, flag_no_active_voucher) - return res, nil - } - - res.FlagReset = append(res.FlagReset, flag_no_active_voucher) - - // Check if user has an active voucher with proper error handling - activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) - if err != nil { - if db.IsNotFound(err) { - // No active voucher, set the first one as default - firstVoucher := vouchersResp[0] - defaultSym := firstVoucher.TokenSymbol - defaultBal := firstVoucher.Balance - defaultDec := firstVoucher.TokenDecimals - defaultAddr := firstVoucher.TokenAddress - - // Scale down the balance - scaledBalance := store.ScaleDownBalance(defaultBal, defaultDec) - - firstVoucherMap := map[storedb.DataTyp]string{ - storedb.DATA_ACTIVE_SYM: defaultSym, - storedb.DATA_ACTIVE_BAL: scaledBalance, - storedb.DATA_ACTIVE_DECIMAL: defaultDec, - storedb.DATA_ACTIVE_ADDRESS: defaultAddr, - } - - for key, value := range firstVoucherMap { - if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { - logg.ErrorCtxf(ctx, "Failed to write active voucher data", "key", key, "error", err) - return res, err - } - err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write voucher db log entry", "key", key, "value", value) - } - } - - logg.InfoCtxf(ctx, "Default voucher set", "symbol", defaultSym, "balance", defaultBal, "decimals", defaultDec, "address", defaultAddr) - } else { - logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) - return res, err - } - } else { - // Update active voucher balance - activeSymStr := string(activeSym) - - // Find the matching voucher data - var activeData *dataserviceapi.TokenHoldings - for _, voucher := range vouchersResp { - if voucher.TokenSymbol == activeSymStr { - activeData = &voucher - break - } - } - - if activeData == nil { - logg.ErrorCtxf(ctx, "activeSym not found in vouchers, setting the first voucher as the default", "activeSym", activeSymStr) - firstVoucher := vouchersResp[0] - activeData = &firstVoucher - } - - // Scale down the balance - scaledBalance := store.ScaleDownBalance(activeData.Balance, activeData.TokenDecimals) - - // Update the balance field with the scaled value - activeData.Balance = scaledBalance - - // Pass the matching voucher data to UpdateVoucherData - if err := store.UpdateVoucherData(ctx, h.userdataStore, sessionId, activeData); err != nil { - logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err) - return res, err - } - } - - // Store all voucher data - data := store.ProcessVouchers(vouchersResp) - dataMap := map[storedb.DataTyp]string{ - storedb.DATA_VOUCHER_SYMBOLS: data.Symbols, - storedb.DATA_VOUCHER_BALANCES: data.Balances, - storedb.DATA_VOUCHER_DECIMALS: data.Decimals, - storedb.DATA_VOUCHER_ADDRESSES: data.Addresses, - } - - // Write data entries - for key, value := range dataMap { - if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { - logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) - continue - } - } - - return res, nil -} - -// GetVoucherList fetches the list of vouchers and formats them. -func (h *MenuHandlers) GetVoucherList(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") - } - - userStore := h.userdataStore - - // Read vouchers from the store - voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS) - logg.InfoCtxf(ctx, "reading voucherData in GetVoucherList", "sessionId", sessionId, "key", storedb.DATA_VOUCHER_SYMBOLS, "voucherData", voucherData) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read voucherData entires with", "key", storedb.DATA_VOUCHER_SYMBOLS, "error", err) - return res, err - } - - voucherBalances, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_BALANCES) - logg.InfoCtxf(ctx, "reading voucherBalances in GetVoucherList", "sessionId", sessionId, "key", storedb.DATA_VOUCHER_BALANCES, "voucherBalances", voucherBalances) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read voucherData entires with", "key", storedb.DATA_VOUCHER_BALANCES, "error", err) - return res, err - } - - formattedVoucherList := store.FormatVoucherList(ctx, string(voucherData), string(voucherBalances)) - finalOutput := strings.Join(formattedVoucherList, "\n") - - logg.InfoCtxf(ctx, "final output for GetVoucherList", "sessionId", sessionId, "finalOutput", finalOutput) - - res.Content = finalOutput - - return res, nil -} - -// ViewVoucher retrieves the token holding and balance from the subprefixDB -// and displays it to the user for them to select it. -func (h *MenuHandlers) ViewVoucher(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") - } - - code := codeFromCtx(ctx) - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - - flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") - - inputStr := string(input) - - metadata, err := store.GetVoucherData(ctx, h.userdataStore, sessionId, inputStr) - if err != nil { - return res, fmt.Errorf("failed to retrieve voucher data: %v", err) - } - - if metadata == nil { - res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) - return res, nil - } - - if err := store.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil { - logg.ErrorCtxf(ctx, "failed on StoreTemporaryVoucher", "error", err) - return res, err - } - - res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) - res.Content = l.Get("Symbol: %s\nBalance: %s", metadata.TokenSymbol, metadata.Balance) - - return res, nil -} - -// SetVoucher retrieves the temp voucher data and sets it as the active data. -func (h *MenuHandlers) SetVoucher(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") - } - - // Get temporary data - tempData, err := store.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId) - if err != nil { - logg.ErrorCtxf(ctx, "failed on GetTemporaryVoucherData", "error", err) - return res, err - } - - // Set as active and clear temporary data - if err := store.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil { - logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err) - return res, err - } - - res.Content = tempData.TokenSymbol - return res, nil -} - -// GetVoucherDetails retrieves the voucher details. -func (h *MenuHandlers) GetVoucherDetails(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_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - - // get the active address - activeAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "error", err) - return res, err - } - - // use the voucher contract address to get the data from the API - voucherData, err := h.accountService.VoucherData(ctx, string(activeAddress)) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - return res, nil - } - res.FlagReset = append(res.FlagReset, flag_api_error) - - res.Content = fmt.Sprintf( - "Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", voucherData.TokenName, voucherData.TokenSymbol, voucherData.TokenCommodity, voucherData.TokenLocation, - ) - - return res, nil -} - -// GetDefaultPool returns the current user's Pool. If none is set, it returns the default config pool. -func (h *MenuHandlers) GetDefaultPool(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") - } - - userStore := h.userdataStore - activePoolSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM) - if err != nil { - if db.IsNotFound(err) { - // set the default as the response - res.Content = config.DefaultPoolSymbol() - return res, nil - } - - logg.ErrorCtxf(ctx, "failed to read the activePoolSym entry with", "key", storedb.DATA_ACTIVE_POOL_SYM, "error", err) - return res, err - } - - res.Content = string(activePoolSym) - - return res, nil -} - -// ViewPool retrieves the pool details from the user store -// and displays it to the user for them to select it. -func (h *MenuHandlers) ViewPool(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") - } - - code := codeFromCtx(ctx) - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - - flag_incorrect_pool, _ := h.flagManager.GetFlag("flag_incorrect_pool") - - inputStr := string(input) - - poolData, err := store.GetPoolData(ctx, h.userdataStore, sessionId, inputStr) - if err != nil { - return res, fmt.Errorf("failed to retrieve pool data: %v", err) - } - - if poolData == nil { - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - - // no match found. Call the API using the inputStr as the symbol - poolResp, err := h.accountService.RetrievePoolDetails(ctx, inputStr) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - return res, nil - } - - if len(poolResp.PoolSymbol) == 0 { - // If the API does not return the data, set the flag - res.FlagSet = append(res.FlagSet, flag_incorrect_pool) - return res, nil - } - - poolData = poolResp - } - - if err := store.StoreTemporaryPool(ctx, h.userdataStore, sessionId, poolData); err != nil { - logg.ErrorCtxf(ctx, "failed on StoreTemporaryPool", "error", err) - return res, err - } - - res.FlagReset = append(res.FlagReset, flag_incorrect_pool) - res.Content = l.Get("Name: %s\nSymbol: %s", poolData.PoolName, poolData.PoolSymbol) - - return res, nil -} - -// SetPool retrieves the temp pool data and sets it as the active data. -func (h *MenuHandlers) SetPool(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") - } - - // Get temporary data - tempData, err := store.GetTemporaryPoolData(ctx, h.userdataStore, sessionId) - if err != nil { - logg.ErrorCtxf(ctx, "failed on GetTemporaryPoolData", "error", err) - return res, err - } - - // Set as active and clear temporary data - if err := store.UpdatePoolData(ctx, h.userdataStore, sessionId, tempData); err != nil { - logg.ErrorCtxf(ctx, "failed on UpdatePoolData", "error", err) - return res, err - } - - res.Content = tempData.PoolSymbol - return res, nil -} - // persistInitialLanguageCode receives an initial language code and persists it to the store func (h *MenuHandlers) persistInitialLanguageCode(ctx context.Context, sessionId string, code string) error { store := h.userdataStore diff --git a/handlers/application/pools.go b/handlers/application/pools.go new file mode 100644 index 0000000..c53fa82 --- /dev/null +++ b/handlers/application/pools.go @@ -0,0 +1,163 @@ +package application + +import ( + "context" + "fmt" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/sarafu-vise/config" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "gopkg.in/leonelquinteros/gotext.v1" +) + +// GetPools fetches a list of 5 top pools +func (h *MenuHandlers) GetPools(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") + } + userStore := h.userdataStore + + flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") + + // call the api to get a list of top 5 pools sorted by swaps + topPools, err := h.accountService.FetchTopPools(ctx) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) + return res, err + } + + // Return if there are no pools + if len(topPools) == 0 { + return res, nil + } + + data := store.ProcessPools(topPools) + + // Store all Pool data + dataMap := map[storedb.DataTyp]string{ + storedb.DATA_POOL_NAMES: data.PoolNames, + storedb.DATA_POOL_SYMBOLS: data.PoolSymbols, + storedb.DATA_POOL_ADDRESSES: data.PoolContractAdrresses, + } + + // Write data entries + for key, value := range dataMap { + if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) + continue + } + } + + res.Content = h.ReplaceSeparatorFunc(data.PoolSymbols) + + return res, nil +} + +// GetDefaultPool returns the current user's Pool. If none is set, it returns the default config pool. +func (h *MenuHandlers) GetDefaultPool(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") + } + + userStore := h.userdataStore + activePoolSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM) + if err != nil { + if db.IsNotFound(err) { + // set the default as the response + res.Content = config.DefaultPoolSymbol() + return res, nil + } + + logg.ErrorCtxf(ctx, "failed to read the activePoolSym entry with", "key", storedb.DATA_ACTIVE_POOL_SYM, "error", err) + return res, err + } + + res.Content = string(activePoolSym) + + return res, nil +} + +// ViewPool retrieves the pool details from the user store +// and displays it to the user for them to select it. +func (h *MenuHandlers) ViewPool(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") + } + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + flag_incorrect_pool, _ := h.flagManager.GetFlag("flag_incorrect_pool") + + inputStr := string(input) + + poolData, err := store.GetPoolData(ctx, h.userdataStore, sessionId, inputStr) + if err != nil { + return res, fmt.Errorf("failed to retrieve pool data: %v", err) + } + + if poolData == nil { + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + + // no match found. Call the API using the inputStr as the symbol + poolResp, err := h.accountService.RetrievePoolDetails(ctx, inputStr) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + return res, nil + } + + if len(poolResp.PoolSymbol) == 0 { + // If the API does not return the data, set the flag + res.FlagSet = append(res.FlagSet, flag_incorrect_pool) + return res, nil + } + + poolData = poolResp + } + + if err := store.StoreTemporaryPool(ctx, h.userdataStore, sessionId, poolData); err != nil { + logg.ErrorCtxf(ctx, "failed on StoreTemporaryPool", "error", err) + return res, err + } + + res.FlagReset = append(res.FlagReset, flag_incorrect_pool) + res.Content = l.Get("Name: %s\nSymbol: %s", poolData.PoolName, poolData.PoolSymbol) + + return res, nil +} + +// SetPool retrieves the temp pool data and sets it as the active data. +func (h *MenuHandlers) SetPool(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") + } + + // Get temporary data + tempData, err := store.GetTemporaryPoolData(ctx, h.userdataStore, sessionId) + if err != nil { + logg.ErrorCtxf(ctx, "failed on GetTemporaryPoolData", "error", err) + return res, err + } + + // Set as active and clear temporary data + if err := store.UpdatePoolData(ctx, h.userdataStore, sessionId, tempData); err != nil { + logg.ErrorCtxf(ctx, "failed on UpdatePoolData", "error", err) + return res, err + } + + res.Content = tempData.PoolSymbol + return res, nil +} diff --git a/handlers/application/poolswap.go b/handlers/application/poolswap.go index 816dfcf..ca728e6 100644 --- a/handlers/application/poolswap.go +++ b/handlers/application/poolswap.go @@ -13,52 +13,6 @@ import ( "gopkg.in/leonelquinteros/gotext.v1" ) -// GetPools fetches a list of 5 top pools -func (h *MenuHandlers) GetPools(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") - } - userStore := h.userdataStore - - flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") - - // call the api to get a list of top 5 pools sorted by swaps - topPools, err := h.accountService.FetchTopPools(ctx) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err) - return res, err - } - - // Return if there are no pools - if len(topPools) == 0 { - return res, nil - } - - data := store.ProcessPools(topPools) - - // Store all Pool data - dataMap := map[storedb.DataTyp]string{ - storedb.DATA_POOL_NAMES: data.PoolNames, - storedb.DATA_POOL_SYMBOLS: data.PoolSymbols, - storedb.DATA_POOL_ADDRESSES: data.PoolContractAdrresses, - } - - // Write data entries - for key, value := range dataMap { - if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { - logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) - continue - } - } - - res.Content = h.ReplaceSeparatorFunc(data.PoolSymbols) - - return res, nil -} - // LoadSwapFromList returns a list of possible vouchers to swap to func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result -- 2.45.2 From eafc0f69c7b216c4ea75ce1f5ccc706dc2169b0e Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 16:34:20 +0300 Subject: [PATCH 11/26] added alias.go for alias related functions --- handlers/application/alias.go | 147 ++++++++++++++++++++++++++++ handlers/application/menuhandler.go | 136 ------------------------- 2 files changed, 147 insertions(+), 136 deletions(-) create mode 100644 handlers/application/alias.go diff --git a/handlers/application/alias.go b/handlers/application/alias.go new file mode 100644 index 0000000..1fffea8 --- /dev/null +++ b/handlers/application/alias.go @@ -0,0 +1,147 @@ +package application + +import ( + "bytes" + "context" + "fmt" + "unicode" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/resource" + + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" +) + +// RequestCustomAlias requests an ENS based alias name based on a user's input,then saves it as temporary value +func (h *MenuHandlers) RequestCustomAlias(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + var alias string + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + if string(input) == "0" { + return res, nil + } + + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + + store := h.userdataStore + aliasHint, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if err != nil { + if db.IsNotFound(err) { + return res, nil + } + return res, err + } + //Ensures that the call doesn't happen twice for the same alias hint + if !bytes.Equal(aliasHint, input) { + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(string(input))) + if err != nil { + return res, err + } + publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + if db.IsNotFound(err) { + return res, nil + } + } + sanitizedInput := sanitizeAliasHint(string(input)) + // Check if an alias already exists + existingAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS) + if err == nil && len(existingAlias) > 0 { + logg.InfoCtxf(ctx, "Current alias", "alias", string(existingAlias)) + // Update existing alias + aliasResult, err := h.accountService.UpdateAlias(ctx, sanitizedInput, string(publicKey)) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed to update alias", "alias", sanitizedInput, "error", err) + return res, nil + } + alias = aliasResult.Alias + logg.InfoCtxf(ctx, "Updated alias", "alias", alias) + } else { + logg.InfoCtxf(ctx, "Registering a new alias", "err", err) + // Register a new alias + aliasResult, err := h.accountService.RequestAlias(ctx, string(publicKey), sanitizedInput) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed to retrieve alias", "alias", sanitizedInput, "error_alias_request", err) + return res, nil + } + res.FlagReset = append(res.FlagReset, flag_api_error) + + alias = aliasResult.Alias + logg.InfoCtxf(ctx, "Suggested alias", "alias", alias) + } + //Store the returned alias,wait for user to confirm it as new account alias + logg.InfoCtxf(ctx, "Final suggested alias", "alias", alias) + err = store.WriteEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS, []byte(alias)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write suggested alias", "key", storedb.DATA_SUGGESTED_ALIAS, "value", alias, "error", err) + return res, err + } + } + return res, nil +} + +func sanitizeAliasHint(input string) string { + for i, r := range input { + // Check if the character is a special character (non-alphanumeric) + if !unicode.IsLetter(r) && !unicode.IsNumber(r) { + return input[:i] + } + } + // If no special character is found, return the whole input + return input +} + +// GetSuggestedAlias loads and displays the suggested alias name from the temporary value +func (h *MenuHandlers) GetSuggestedAlias(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") + } + suggestedAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS) + if err != nil && len(suggestedAlias) <= 0 { + logg.ErrorCtxf(ctx, "failed to read suggested alias", "key", storedb.DATA_SUGGESTED_ALIAS, "error", err) + return res, nil + } + res.Content = string(suggestedAlias) + return res, nil +} + +// ConfirmNewAlias reads the suggested alias from the [DATA_SUGGECTED_ALIAS] key and confirms it as the new account alias. +func (h *MenuHandlers) ConfirmNewAlias(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + store := h.userdataStore + logdb := h.logDb + + flag_alias_set, _ := h.flagManager.GetFlag("flag_alias_set") + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + newAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS) + if err != nil { + return res, nil + } + logg.InfoCtxf(ctx, "Confirming new alias", "alias", string(newAlias)) + err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(string(newAlias))) + if err != nil { + logg.ErrorCtxf(ctx, "failed to clear DATA_ACCOUNT_ALIAS_VALUE entry with", "key", storedb.DATA_ACCOUNT_ALIAS, "value", "empty", "error", err) + return res, err + } + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(newAlias)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write account alias db log entry", "key", storedb.DATA_ACCOUNT_ALIAS, "value", newAlias) + } + + res.FlagSet = append(res.FlagSet, flag_alias_set) + return res, nil +} diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 110595f..36b5258 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -1,13 +1,11 @@ package application import ( - "bytes" "context" "fmt" "path" "strconv" "strings" - "unicode" "gopkg.in/leonelquinteros/gotext.v1" @@ -590,140 +588,6 @@ func (h *MenuHandlers) persistLanguageCode(ctx context.Context, code string) err return h.persistInitialLanguageCode(ctx, sessionId, code) } -// RequestCustomAlias requests an ENS based alias name based on a user's input,then saves it as temporary value -func (h *MenuHandlers) RequestCustomAlias(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - var alias string - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - if string(input) == "0" { - return res, nil - } - - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - - store := h.userdataStore - aliasHint, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if err != nil { - if db.IsNotFound(err) { - return res, nil - } - return res, err - } - //Ensures that the call doesn't happen twice for the same alias hint - if !bytes.Equal(aliasHint, input) { - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(string(input))) - if err != nil { - return res, err - } - publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - if db.IsNotFound(err) { - return res, nil - } - } - sanitizedInput := sanitizeAliasHint(string(input)) - // Check if an alias already exists - existingAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS) - if err == nil && len(existingAlias) > 0 { - logg.InfoCtxf(ctx, "Current alias", "alias", string(existingAlias)) - // Update existing alias - aliasResult, err := h.accountService.UpdateAlias(ctx, sanitizedInput, string(publicKey)) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed to update alias", "alias", sanitizedInput, "error", err) - return res, nil - } - alias = aliasResult.Alias - logg.InfoCtxf(ctx, "Updated alias", "alias", alias) - } else { - logg.InfoCtxf(ctx, "Registering a new alias", "err", err) - // Register a new alias - aliasResult, err := h.accountService.RequestAlias(ctx, string(publicKey), sanitizedInput) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed to retrieve alias", "alias", sanitizedInput, "error_alias_request", err) - return res, nil - } - res.FlagReset = append(res.FlagReset, flag_api_error) - - alias = aliasResult.Alias - logg.InfoCtxf(ctx, "Suggested alias", "alias", alias) - } - //Store the returned alias,wait for user to confirm it as new account alias - logg.InfoCtxf(ctx, "Final suggested alias", "alias", alias) - err = store.WriteEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS, []byte(alias)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write suggested alias", "key", storedb.DATA_SUGGESTED_ALIAS, "value", alias, "error", err) - return res, err - } - } - return res, nil -} - -func sanitizeAliasHint(input string) string { - for i, r := range input { - // Check if the character is a special character (non-alphanumeric) - if !unicode.IsLetter(r) && !unicode.IsNumber(r) { - return input[:i] - } - } - // If no special character is found, return the whole input - return input -} - -// GetSuggestedAlias loads and displays the suggested alias name from the temporary value -func (h *MenuHandlers) GetSuggestedAlias(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") - } - suggestedAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS) - if err != nil && len(suggestedAlias) <= 0 { - logg.ErrorCtxf(ctx, "failed to read suggested alias", "key", storedb.DATA_SUGGESTED_ALIAS, "error", err) - return res, nil - } - res.Content = string(suggestedAlias) - return res, nil -} - -// ConfirmNewAlias reads the suggested alias from the [DATA_SUGGECTED_ALIAS] key and confirms it as the new account alias. -func (h *MenuHandlers) ConfirmNewAlias(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - store := h.userdataStore - logdb := h.logDb - - flag_alias_set, _ := h.flagManager.GetFlag("flag_alias_set") - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - newAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS) - if err != nil { - return res, nil - } - logg.InfoCtxf(ctx, "Confirming new alias", "alias", string(newAlias)) - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(string(newAlias))) - if err != nil { - logg.ErrorCtxf(ctx, "failed to clear DATA_ACCOUNT_ALIAS_VALUE entry with", "key", storedb.DATA_ACCOUNT_ALIAS, "value", "empty", "error", err) - return res, err - } - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(newAlias)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write account alias db log entry", "key", storedb.DATA_ACCOUNT_ALIAS, "value", newAlias) - } - - res.FlagSet = append(res.FlagSet, flag_alias_set) - return res, nil -} - // ClearTemporaryValue empties the DATA_TEMPORARY_VALUE at the main menu to prevent // previously stored data from being accessed func (h *MenuHandlers) ClearTemporaryValue(ctx context.Context, sym string, input []byte) (resource.Result, error) { -- 2.45.2 From 8297f012f47624674a10b8288d4e758650e70bba Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 16:39:44 +0300 Subject: [PATCH 12/26] added balance.go for balance related functions --- handlers/application/balance.go | 105 ++++++++++++++++++++++++++++ handlers/application/menuhandler.go | 92 ------------------------ 2 files changed, 105 insertions(+), 92 deletions(-) create mode 100644 handlers/application/balance.go diff --git a/handlers/application/balance.go b/handlers/application/balance.go new file mode 100644 index 0000000..f36a6a3 --- /dev/null +++ b/handlers/application/balance.go @@ -0,0 +1,105 @@ +package application + +import ( + "context" + "fmt" + "strings" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "gopkg.in/leonelquinteros/gotext.v1" +) + +// CheckBalance retrieves the balance of the active voucher and sets +// the balance as the result content. +func (h *MenuHandlers) CheckBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var ( + res resource.Result + err error + alias string + content string + ) + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + store := h.userdataStore + + // get the active sym and active balance + activeSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) + if err != nil { + logg.InfoCtxf(ctx, "could not find the activeSym in checkBalance:", "err", err) + if !db.IsNotFound(err) { + logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) + return res, err + } + } + + activeBal, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL) + if err != nil { + if !db.IsNotFound(err) { + logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err) + return res, err + } + } + + accAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS) + if err != nil { + if !db.IsNotFound(err) { + logg.ErrorCtxf(ctx, "failed to read account alias entry with", "key", storedb.DATA_ACCOUNT_ALIAS, "error", err) + return res, err + } + } else { + alias = strings.Split(string(accAlias), ".")[0] + } + + content, err = loadUserContent(ctx, string(activeSym), string(activeBal), alias) + if err != nil { + return res, err + } + res.Content = content + + return res, nil +} + +// loadUserContent loads the main user content in the main menu: the alias,balance associated with active voucher +func loadUserContent(ctx context.Context, activeSym string, balance string, alias string) (string, error) { + var content string + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + // Format the balance to 2 decimal places or default to 0.00 + formattedAmount, err := store.TruncateDecimalString(balance, 2) + if err != nil { + formattedAmount = "0.00" + } + + // format the final output + balStr := fmt.Sprintf("%s %s", formattedAmount, activeSym) + if alias != "" { + content = l.Get("%s balance: %s\n", alias, balStr) + } else { + content = l.Get("Balance: %s\n", balStr) + } + return content, nil +} + +// FetchCommunityBalance retrieves and displays the balance for community accounts in user's preferred language. +func (h *MenuHandlers) FetchCommunityBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + // retrieve the language code from the context + code := codeFromCtx(ctx) + // Initialize the localization system with the appropriate translation directory + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + //TODO: + //Check if the address is a community account,if then,get the actual balance + res.Content = l.Get("Community Balance: 0.00") + return res, nil +} diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 36b5258..4ab6a8e 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -463,98 +463,6 @@ func (h *MenuHandlers) ShowBlockedAccount(ctx context.Context, sym string, input return res, nil } -// loadUserContent loads the main user content in the main menu: the alias,balance associated with active voucher -func loadUserContent(ctx context.Context, activeSym string, balance string, alias string) (string, error) { - var content string - - code := codeFromCtx(ctx) - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - - // Format the balance to 2 decimal places or default to 0.00 - formattedAmount, err := store.TruncateDecimalString(balance, 2) - if err != nil { - formattedAmount = "0.00" - } - - // format the final output - balStr := fmt.Sprintf("%s %s", formattedAmount, activeSym) - if alias != "" { - content = l.Get("%s balance: %s\n", alias, balStr) - } else { - content = l.Get("Balance: %s\n", balStr) - } - return content, nil -} - -// CheckBalance retrieves the balance of the active voucher and sets -// the balance as the result content. -func (h *MenuHandlers) CheckBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var ( - res resource.Result - err error - alias string - content string - ) - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - store := h.userdataStore - - // get the active sym and active balance - activeSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) - if err != nil { - logg.InfoCtxf(ctx, "could not find the activeSym in checkBalance:", "err", err) - if !db.IsNotFound(err) { - logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) - return res, err - } - } - - activeBal, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL) - if err != nil { - if !db.IsNotFound(err) { - logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err) - return res, err - } - } - - accAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS) - if err != nil { - if !db.IsNotFound(err) { - logg.ErrorCtxf(ctx, "failed to read account alias entry with", "key", storedb.DATA_ACCOUNT_ALIAS, "error", err) - return res, err - } - } else { - alias = strings.Split(string(accAlias), ".")[0] - } - - content, err = loadUserContent(ctx, string(activeSym), string(activeBal), alias) - if err != nil { - return res, err - } - res.Content = content - - return res, nil -} - -// FetchCommunityBalance retrieves and displays the balance for community accounts in user's preferred language. -func (h *MenuHandlers) FetchCommunityBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - // retrieve the language code from the context - code := codeFromCtx(ctx) - // Initialize the localization system with the appropriate translation directory - l := gotext.NewLocale(translationDir, code) - l.AddDomain("default") - //TODO: - //Check if the address is a community account,if then,get the actual balance - res.Content = l.Get("Community Balance: 0.00") - return res, nil -} - // persistInitialLanguageCode receives an initial language code and persists it to the store func (h *MenuHandlers) persistInitialLanguageCode(ctx context.Context, sessionId string, code string) error { store := h.userdataStore -- 2.45.2 From b9aae610db421f80ebd308a8aac3cd9e678fbc3a Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 16:43:43 +0300 Subject: [PATCH 13/26] added language.go for language related functions --- handlers/application/language.go | 74 +++++++++++++++++++++++++++++ handlers/application/menuhandler.go | 63 ------------------------ 2 files changed, 74 insertions(+), 63 deletions(-) create mode 100644 handlers/application/language.go diff --git a/handlers/application/language.go b/handlers/application/language.go new file mode 100644 index 0000000..dcbe985 --- /dev/null +++ b/handlers/application/language.go @@ -0,0 +1,74 @@ +package application + +import ( + "context" + "fmt" + "strings" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/state" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + commonlang "git.grassecon.net/grassrootseconomics/common/lang" +) + +// SetLanguage sets the language across the menu. +func (h *MenuHandlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + symbol, _ := h.st.Where() + code := strings.Split(symbol, "_")[1] + + // TODO: Use defaultlanguage from config + if !commonlang.IsValidISO639(code) { + //Fallback to english instead? + code = "eng" + } + err := h.persistLanguageCode(ctx, code) + if err != nil { + return res, err + } + res.Content = code + res.FlagSet = append(res.FlagSet, state.FLAG_LANG) + languageSetFlag, err := h.flagManager.GetFlag("flag_language_set") + if err != nil { + logg.ErrorCtxf(ctx, "Error setting the languageSetFlag", "error", err) + return res, err + } + res.FlagSet = append(res.FlagSet, languageSetFlag) + + return res, nil +} + +// persistLanguageCode persists the selected ISO 639 language code +func (h *MenuHandlers) persistLanguageCode(ctx context.Context, code string) error { + store := h.userdataStore + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return fmt.Errorf("missing session") + } + err := store.WriteEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE, []byte(code)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to persist language code", "key", storedb.DATA_SELECTED_LANGUAGE_CODE, "value", code, "error", err) + return err + } + return h.persistInitialLanguageCode(ctx, sessionId, code) +} + +// persistInitialLanguageCode receives an initial language code and persists it to the store +func (h *MenuHandlers) persistInitialLanguageCode(ctx context.Context, sessionId string, code string) error { + store := h.userdataStore + _, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE) + if err == nil { + return nil + } + if !db.IsNotFound(err) { + return err + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE, []byte(code)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to persist initial language code", "key", storedb.DATA_INITIAL_LANGUAGE_CODE, "value", code, "error", err) + return err + } + return nil +} diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 4ab6a8e..648cc76 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -5,7 +5,6 @@ import ( "fmt" "path" "strconv" - "strings" "gopkg.in/leonelquinteros/gotext.v1" @@ -17,7 +16,6 @@ import ( "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/state" - commonlang "git.grassecon.net/grassrootseconomics/common/lang" "git.grassecon.net/grassrootseconomics/common/pin" "git.grassecon.net/grassrootseconomics/sarafu-api/remote" "git.grassecon.net/grassrootseconomics/sarafu-vise/internal/sms" @@ -158,34 +156,6 @@ func codeFromCtx(ctx context.Context) string { return code } -// SetLanguage sets the language across the menu. -func (h *MenuHandlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - - symbol, _ := h.st.Where() - code := strings.Split(symbol, "_")[1] - - // TODO: Use defaultlanguage from config - if !commonlang.IsValidISO639(code) { - //Fallback to english instead? - code = "eng" - } - err := h.persistLanguageCode(ctx, code) - if err != nil { - return res, err - } - res.Content = code - res.FlagSet = append(res.FlagSet, state.FLAG_LANG) - languageSetFlag, err := h.flagManager.GetFlag("flag_language_set") - if err != nil { - logg.ErrorCtxf(ctx, "Error setting the languageSetFlag", "error", err) - return res, err - } - res.FlagSet = append(res.FlagSet, languageSetFlag) - - return res, nil -} - func (h *MenuHandlers) CheckAccountCreated(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result flag_language_set, _ := h.flagManager.GetFlag("flag_language_set") @@ -463,39 +433,6 @@ func (h *MenuHandlers) ShowBlockedAccount(ctx context.Context, sym string, input return res, nil } -// persistInitialLanguageCode receives an initial language code and persists it to the store -func (h *MenuHandlers) persistInitialLanguageCode(ctx context.Context, sessionId string, code string) error { - store := h.userdataStore - _, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE) - if err == nil { - return nil - } - if !db.IsNotFound(err) { - return err - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE, []byte(code)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to persist initial language code", "key", storedb.DATA_INITIAL_LANGUAGE_CODE, "value", code, "error", err) - return err - } - return nil -} - -// persistLanguageCode persists the selected ISO 639 language code -func (h *MenuHandlers) persistLanguageCode(ctx context.Context, code string) error { - store := h.userdataStore - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return fmt.Errorf("missing session") - } - err := store.WriteEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE, []byte(code)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to persist language code", "key", storedb.DATA_SELECTED_LANGUAGE_CODE, "value", code, "error", err) - return err - } - return h.persistInitialLanguageCode(ctx, sessionId, code) -} - // ClearTemporaryValue empties the DATA_TEMPORARY_VALUE at the main menu to prevent // previously stored data from being accessed func (h *MenuHandlers) ClearTemporaryValue(ctx context.Context, sym string, input []byte) (resource.Result, error) { -- 2.45.2 From 2b557b27cf5bce83941be33022866c566012daab Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 16:52:59 +0300 Subject: [PATCH 14/26] added authorization.go for authorization related functions --- handlers/application/authorization.go | 77 +++++++++++++++++++++++++++ handlers/application/menuhandler.go | 68 ----------------------- 2 files changed, 77 insertions(+), 68 deletions(-) create mode 100644 handlers/application/authorization.go diff --git a/handlers/application/authorization.go b/handlers/application/authorization.go new file mode 100644 index 0000000..2087632 --- /dev/null +++ b/handlers/application/authorization.go @@ -0,0 +1,77 @@ +package application + +import ( + "context" + "fmt" + + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/common/pin" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" +) + +// Authorize attempts to unlock the next sequential nodes by verifying the provided PIN against the already set PIN. +// It sets the required flags that control the flow. +func (h *MenuHandlers) Authorize(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") + flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + + pinInput := string(input) + + if !pin.IsValidPIN(pinInput) { + res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_allow_update) + return res, nil + } + + store := h.userdataStore + AccountPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read AccountPin entry with", "key", storedb.DATA_ACCOUNT_PIN, "error", err) + return res, err + } + + // verify that the user provided the correct PIN + if pin.VerifyPIN(string(AccountPin), pinInput) { + // set the required flags for a valid PIN + res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized) + res.FlagReset = append(res.FlagReset, flag_incorrect_pin) + + err := h.resetIncorrectPINAttempts(ctx, sessionId) + if err != nil { + return res, err + } + } else { + // set the required flags for an incorrect PIN + res.FlagSet = append(res.FlagSet, flag_incorrect_pin) + res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_allow_update) + + err = h.incrementIncorrectPINAttempts(ctx, sessionId) + if err != nil { + return res, err + } + } + + return res, nil +} + +// ResetAllowUpdate resets the allowupdate flag that allows a user to update profile data. +func (h *MenuHandlers) ResetAllowUpdate(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + res.FlagReset = append(res.FlagReset, flag_allow_update) + return res, nil +} + +// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry. +func (h *MenuHandlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") + res.FlagReset = append(res.FlagReset, flag_account_authorized) + return res, nil +} diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 648cc76..084facd 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -16,7 +16,6 @@ import ( "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/state" - "git.grassecon.net/grassrootseconomics/common/pin" "git.grassecon.net/grassrootseconomics/sarafu-api/remote" "git.grassecon.net/grassrootseconomics/sarafu-vise/internal/sms" "git.grassecon.net/grassrootseconomics/sarafu-vise/profile" @@ -243,22 +242,6 @@ func (h *MenuHandlers) ResetUnregisteredNumber(ctx context.Context, sym string, return res, nil } -// ResetAllowUpdate resets the allowupdate flag that allows a user to update profile data. -func (h *MenuHandlers) ResetAllowUpdate(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") - res.FlagReset = append(res.FlagReset, flag_allow_update) - return res, nil -} - -// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry. -func (h *MenuHandlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") - res.FlagReset = append(res.FlagReset, flag_account_authorized) - return res, nil -} - // CheckIdentifier retrieves the Public key from the userdatastore under the key: DATA_PUBLIC_KEY and triggers an sms that // will be sent to the associated session id func (h *MenuHandlers) CheckIdentifier(ctx context.Context, sym string, input []byte) (resource.Result, error) { @@ -287,57 +270,6 @@ func (h *MenuHandlers) CheckIdentifier(ctx context.Context, sym string, input [] return res, nil } -// Authorize attempts to unlock the next sequential nodes by verifying the provided PIN against the already set PIN. -// It sets the required flags that control the flow. -func (h *MenuHandlers) Authorize(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") - flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") - flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") - - pinInput := string(input) - - if !pin.IsValidPIN(pinInput) { - res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_allow_update) - return res, nil - } - - store := h.userdataStore - AccountPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read AccountPin entry with", "key", storedb.DATA_ACCOUNT_PIN, "error", err) - return res, err - } - - // verify that the user provided the correct PIN - if pin.VerifyPIN(string(AccountPin), pinInput) { - // set the required flags for a valid PIN - res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized) - res.FlagReset = append(res.FlagReset, flag_incorrect_pin) - - err := h.resetIncorrectPINAttempts(ctx, sessionId) - if err != nil { - return res, err - } - } else { - // set the required flags for an incorrect PIN - res.FlagSet = append(res.FlagSet, flag_incorrect_pin) - res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_allow_update) - - err = h.incrementIncorrectPINAttempts(ctx, sessionId) - if err != nil { - return res, err - } - } - - return res, nil -} - // Setback sets the flag_back_set flag when the navigation is back. func (h *MenuHandlers) SetBack(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result -- 2.45.2 From 827debe020436319721a3b99d8f5f51ae0656cce Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 16:55:57 +0300 Subject: [PATCH 15/26] added account_status.go for account status related functions --- handlers/application/account_status.go | 122 +++++++++++++++++++++++++ handlers/application/menuhandler.go | 112 ----------------------- 2 files changed, 122 insertions(+), 112 deletions(-) create mode 100644 handlers/application/account_status.go diff --git a/handlers/application/account_status.go b/handlers/application/account_status.go new file mode 100644 index 0000000..845107b --- /dev/null +++ b/handlers/application/account_status.go @@ -0,0 +1,122 @@ +package application + +import ( + "context" + "fmt" + "strconv" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/resource" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" +) + +// CheckAccountStatus queries the API using the TrackingId and sets flags +// based on the account status. +func (h *MenuHandlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + flag_account_success, _ := h.flagManager.GetFlag("flag_account_success") + flag_account_pending, _ := h.flagManager.GetFlag("flag_account_pending") + flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + store := h.userdataStore + publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) + return res, err + } + + r, err := h.accountService.TrackAccountStatus(ctx, string(publicKey)) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_api_error) + logg.ErrorCtxf(ctx, "failed on TrackAccountStatus", "error", err) + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_api_error) + + if r.Active { + res.FlagSet = append(res.FlagSet, flag_account_success) + res.FlagReset = append(res.FlagReset, flag_account_pending) + } else { + res.FlagReset = append(res.FlagReset, flag_account_success) + res.FlagSet = append(res.FlagSet, flag_account_pending) + } + + return res, nil +} + +func (h *MenuHandlers) CheckAccountCreated(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + flag_language_set, _ := h.flagManager.GetFlag("flag_language_set") + flag_account_created, _ := h.flagManager.GetFlag("flag_account_created") + + store := h.userdataStore + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + _, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + if db.IsNotFound(err) { + // reset major flags + res.FlagReset = append(res.FlagReset, flag_language_set) + res.FlagReset = append(res.FlagReset, flag_account_created) + + return res, nil + } + + return res, nil + } + + res.FlagSet = append(res.FlagSet, flag_account_created) + return res, nil +} + +// CheckBlockedStatus: +// 1. Checks whether the DATA_SELF_PIN_RESET is 1 and sets the flag_account_pin_reset +// 2. resets the account blocked flag if the PIN attempts have been reset by an admin. +func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + store := h.userdataStore + + flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked") + flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset") + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + res.FlagReset = append(res.FlagReset, flag_account_pin_reset) + + selfPinReset, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET) + if err == nil { + pinResetValue, _ := strconv.ParseUint(string(selfPinReset), 0, 64) + if pinResetValue == 1 { + res.FlagSet = append(res.FlagSet, flag_account_pin_reset) + } + } + + currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) + if err != nil { + if !db.IsNotFound(err) { + return res, nil + } + } + + pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) + if pinAttemptsValue == 0 { + res.FlagReset = append(res.FlagReset, flag_account_blocked) + return res, nil + } + + return res, nil +} diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 084facd..4c7b4ec 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "path" - "strconv" "gopkg.in/leonelquinteros/gotext.v1" @@ -155,76 +154,6 @@ func codeFromCtx(ctx context.Context) string { return code } -func (h *MenuHandlers) CheckAccountCreated(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - flag_language_set, _ := h.flagManager.GetFlag("flag_language_set") - flag_account_created, _ := h.flagManager.GetFlag("flag_account_created") - - store := h.userdataStore - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - _, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - if db.IsNotFound(err) { - // reset major flags - res.FlagReset = append(res.FlagReset, flag_language_set) - res.FlagReset = append(res.FlagReset, flag_account_created) - - return res, nil - } - - return res, nil - } - - res.FlagSet = append(res.FlagSet, flag_account_created) - return res, nil -} - -// CheckBlockedStatus: -// 1. Checks whether the DATA_SELF_PIN_RESET is 1 and sets the flag_account_pin_reset -// 2. resets the account blocked flag if the PIN attempts have been reset by an admin. -func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - store := h.userdataStore - - flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked") - flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset") - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - res.FlagReset = append(res.FlagReset, flag_account_pin_reset) - - selfPinReset, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET) - if err == nil { - pinResetValue, _ := strconv.ParseUint(string(selfPinReset), 0, 64) - if pinResetValue == 1 { - res.FlagSet = append(res.FlagSet, flag_account_pin_reset) - } - } - - currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) - if err != nil { - if !db.IsNotFound(err) { - return res, nil - } - } - - pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) - if pinAttemptsValue == 0 { - res.FlagReset = append(res.FlagReset, flag_account_blocked) - return res, nil - } - - return res, nil -} - // ResetApiCallFailure resets the api call failure flag func (h *MenuHandlers) ResetApiCallFailure(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -284,47 +213,6 @@ func (h *MenuHandlers) SetBack(ctx context.Context, sym string, input []byte) (r return res, nil } -// CheckAccountStatus queries the API using the TrackingId and sets flags -// based on the account status. -func (h *MenuHandlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - - flag_account_success, _ := h.flagManager.GetFlag("flag_account_success") - flag_account_pending, _ := h.flagManager.GetFlag("flag_account_pending") - flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - store := h.userdataStore - publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } - - r, err := h.accountService.TrackAccountStatus(ctx, string(publicKey)) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_api_error) - logg.ErrorCtxf(ctx, "failed on TrackAccountStatus", "error", err) - return res, nil - } - - res.FlagReset = append(res.FlagReset, flag_api_error) - - if r.Active { - res.FlagSet = append(res.FlagSet, flag_account_success) - res.FlagReset = append(res.FlagReset, flag_account_pending) - } else { - res.FlagReset = append(res.FlagReset, flag_account_success) - res.FlagSet = append(res.FlagSet, flag_account_pending) - } - - return res, nil -} - // Quit displays the Thank you message and exits the menu. func (h *MenuHandlers) Quit(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result -- 2.45.2 From f22f86a2fb85d989f10548d9dda0e0c7f1b976ad Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 17:02:36 +0300 Subject: [PATCH 16/26] move PIN tests to pin_test.go --- handlers/application/menuhandler_test.go | 277 --------------------- handlers/application/pin_test.go | 293 +++++++++++++++++++++++ 2 files changed, 293 insertions(+), 277 deletions(-) create mode 100644 handlers/application/pin_test.go diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index 6161f41..83c23ce 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -621,67 +621,6 @@ func TestSaveGender(t *testing.T) { } } -func TestSaveTemporaryPin(t *testing.T) { - sessionId := "session123" - - ctx, userdatastore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - _, logdb := InitializeTestLogdbStore(t) - logDb := store.LogDb{ - Db: logdb, - } - - fm, err := NewFlagManager(flagsPath) - if err != nil { - log.Fatal(err) - } - - flag_invalid_pin, _ := fm.GetFlag("flag_invalid_pin") - - // Create the MenuHandlers instance with the mock flag manager - h := &MenuHandlers{ - flagManager: fm, - userdataStore: userdatastore, - logDb: logDb, - } - - // Define test cases - tests := []struct { - name string - input []byte - expectedResult resource.Result - }{ - { - name: "Valid Pin entry", - input: []byte("1234"), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_invalid_pin}, - }, - }, - { - name: "Invalid Pin entry", - input: []byte("12343"), - expectedResult: resource.Result{ - FlagSet: []uint32{flag_invalid_pin}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Call the method - res, err := h.SaveTemporaryPin(ctx, "save_pin", tt.input) - - if err != nil { - t.Error(err) - } - // Assert that the Result FlagSet has the required flags after language switch - assert.Equal(t, res, tt.expectedResult, "Result should match expected result") - }) - } -} - func TestCheckIdentifier(t *testing.T) { sessionId := "session123" ctx, userdatastore := InitializeTestStore(t) @@ -2001,65 +1940,6 @@ func TestGetProfile(t *testing.T) { } } -func TestConfirmPinChange(t *testing.T) { - sessionId := "session123" - - mockState := state.NewState(16) - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - fm, _ := NewFlagManager(flagsPath) - flag_pin_mismatch, _ := fm.GetFlag("flag_pin_mismatch") - flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset") - - mockAccountService := new(mocks.MockAccountService) - h := &MenuHandlers{ - userdataStore: store, - flagManager: fm, - accountService: mockAccountService, - st: mockState, - } - - tests := []struct { - name string - input []byte - temporarypin string - expectedResult resource.Result - }{ - { - name: "Test with correct pin confirmation", - input: []byte("1234"), - temporarypin: "1234", - expectedResult: resource.Result{ - FlagReset: []uint32{flag_pin_mismatch, flag_account_pin_reset}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Hash the PIN - hashedPIN, err := pin.HashPIN(tt.temporarypin) - if err != nil { - logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err) - t.Fatal(err) - } - - // Set up the expected behavior of the mock - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) - if err != nil { - t.Fatal(err) - } - - //Call the function under test - res, _ := h.ConfirmPinChange(ctx, "confirm_pin_change", tt.input) - - //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") - - }) - } -} - func TestFetchCommunityBalance(t *testing.T) { // Define test data @@ -2371,56 +2251,6 @@ func TestGetVoucherDetails(t *testing.T) { assert.Equal(t, expectedResult, res) } -func TestCountIncorrectPINAttempts(t *testing.T) { - ctx, store := InitializeTestStore(t) - sessionId := "session123" - ctx = context.WithValue(ctx, "SessionId", sessionId) - attempts := uint8(2) - - h := &MenuHandlers{ - userdataStore: store, - } - err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(attempts)))) - if err != nil { - t.Logf(err.Error()) - } - err = h.incrementIncorrectPINAttempts(ctx, sessionId) - if err != nil { - t.Logf(err.Error()) - } - - attemptsAfterCount, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) - if err != nil { - t.Logf(err.Error()) - } - pinAttemptsValue, _ := strconv.ParseUint(string(attemptsAfterCount), 0, 64) - pinAttemptsCount := uint8(pinAttemptsValue) - expectedAttempts := attempts + 1 - assert.Equal(t, pinAttemptsCount, expectedAttempts) -} - -func TestResetIncorrectPINAttempts(t *testing.T) { - ctx, store := InitializeTestStore(t) - sessionId := "session123" - ctx = context.WithValue(ctx, "SessionId", sessionId) - - err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("2"))) - if err != nil { - t.Logf(err.Error()) - } - - h := &MenuHandlers{ - userdataStore: store, - } - h.resetIncorrectPINAttempts(ctx, sessionId) - incorrectAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) - - if err != nil { - t.Logf(err.Error()) - } - assert.Equal(t, "0", string(incorrectAttempts)) -} - func TestPersistLanguageCode(t *testing.T) { ctx, store := InitializeTestStore(t) @@ -2955,80 +2785,6 @@ func TestShowBlockedAccount(t *testing.T) { } } -func TestValidateBlockedNumber(t *testing.T) { - sessionId := "session123" - validNumber := "+254712345678" - invalidNumber := "12343" // Invalid phone number - unregisteredNumber := "+254734567890" // Valid but unregistered number - publicKey := "0X13242618721" - mockState := state.NewState(128) - - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Fatal(err) - } - flag_unregistered_number, _ := fm.GetFlag("flag_unregistered_number") - - h := &MenuHandlers{ - userdataStore: userStore, - st: mockState, - flagManager: fm, - } - - err = userStore.WriteEntry(ctx, validNumber, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) - if err != nil { - t.Fatal(err) - } - - tests := []struct { - name string - input []byte - expectedResult resource.Result - }{ - { - name: "Valid and registered number", - input: []byte(validNumber), - expectedResult: resource.Result{}, - }, - { - name: "Invalid Phone Number", - input: []byte(invalidNumber), - expectedResult: resource.Result{ - FlagSet: []uint32{flag_unregistered_number}, - }, - }, - { - name: "Unregistered Phone Number", - input: []byte(unregisteredNumber), - expectedResult: resource.Result{ - FlagSet: []uint32{flag_unregistered_number}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - res, err := h.ValidateBlockedNumber(ctx, "validate_blocked_number", tt.input) - - assert.NoError(t, err) - - assert.Equal(t, tt.expectedResult, res) - - if tt.name == "Valid and registered number" { - blockedNumber, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, validNumber, string(blockedNumber)) - } - }) - } -} - func TestGetCurrentProfileInfo(t *testing.T) { sessionId := "session123" ctx, store := InitializeTestStore(t) @@ -3152,39 +2908,6 @@ func TestGetCurrentProfileInfo(t *testing.T) { } } -func TestResetOthersPin(t *testing.T) { - sessionId := "session123" - blockedNumber := "+254712345678" - testPin := "1234" - - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - hashedPIN, err := pin.HashPIN(testPin) - if err != nil { - logg.ErrorCtxf(ctx, "failed to hash testPin", "error", err) - t.Fatal(err) - } - - h := &MenuHandlers{ - userdataStore: userStore, - } - - // Write initial data to the store - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(blockedNumber)) - if err != nil { - t.Fatal(err) - } - err = userStore.WriteEntry(ctx, blockedNumber, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) - if err != nil { - t.Fatal(err) - } - - _, err = h.ResetOthersPin(ctx, "reset_others_pin", []byte("")) - - assert.NoError(t, err) -} - func TestResetUnregisteredNumber(t *testing.T) { ctx := context.Background() diff --git a/handlers/application/pin_test.go b/handlers/application/pin_test.go new file mode 100644 index 0000000..4079aa9 --- /dev/null +++ b/handlers/application/pin_test.go @@ -0,0 +1,293 @@ +package application + +import ( + "context" + "log" + "strconv" + "testing" + + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/state" + "git.grassecon.net/grassrootseconomics/common/pin" + "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/alecthomas/assert/v2" +) + +func TestCountIncorrectPINAttempts(t *testing.T) { + ctx, store := InitializeTestStore(t) + sessionId := "session123" + ctx = context.WithValue(ctx, "SessionId", sessionId) + attempts := uint8(2) + + h := &MenuHandlers{ + userdataStore: store, + } + err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(attempts)))) + if err != nil { + t.Logf(err.Error()) + } + err = h.incrementIncorrectPINAttempts(ctx, sessionId) + if err != nil { + t.Logf(err.Error()) + } + + attemptsAfterCount, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) + if err != nil { + t.Logf(err.Error()) + } + pinAttemptsValue, _ := strconv.ParseUint(string(attemptsAfterCount), 0, 64) + pinAttemptsCount := uint8(pinAttemptsValue) + expectedAttempts := attempts + 1 + assert.Equal(t, pinAttemptsCount, expectedAttempts) +} + +func TestResetIncorrectPINAttempts(t *testing.T) { + ctx, store := InitializeTestStore(t) + sessionId := "session123" + ctx = context.WithValue(ctx, "SessionId", sessionId) + + err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("2"))) + if err != nil { + t.Logf(err.Error()) + } + + h := &MenuHandlers{ + userdataStore: store, + } + h.resetIncorrectPINAttempts(ctx, sessionId) + incorrectAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) + + if err != nil { + t.Logf(err.Error()) + } + assert.Equal(t, "0", string(incorrectAttempts)) +} + +func TestSaveTemporaryPin(t *testing.T) { + sessionId := "session123" + + ctx, userdatastore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + _, logdb := InitializeTestLogdbStore(t) + logDb := store.LogDb{ + Db: logdb, + } + + fm, err := NewFlagManager(flagsPath) + if err != nil { + log.Fatal(err) + } + + flag_invalid_pin, _ := fm.GetFlag("flag_invalid_pin") + + // Create the MenuHandlers instance with the mock flag manager + h := &MenuHandlers{ + flagManager: fm, + userdataStore: userdatastore, + logDb: logDb, + } + + // Define test cases + tests := []struct { + name string + input []byte + expectedResult resource.Result + }{ + { + name: "Valid Pin entry", + input: []byte("1234"), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_invalid_pin}, + }, + }, + { + name: "Invalid Pin entry", + input: []byte("12343"), + expectedResult: resource.Result{ + FlagSet: []uint32{flag_invalid_pin}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Call the method + res, err := h.SaveTemporaryPin(ctx, "save_pin", tt.input) + + if err != nil { + t.Error(err) + } + // Assert that the Result FlagSet has the required flags after language switch + assert.Equal(t, res, tt.expectedResult, "Result should match expected result") + }) + } +} + +func TestConfirmPinChange(t *testing.T) { + sessionId := "session123" + + mockState := state.NewState(16) + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + fm, _ := NewFlagManager(flagsPath) + flag_pin_mismatch, _ := fm.GetFlag("flag_pin_mismatch") + flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset") + + mockAccountService := new(mocks.MockAccountService) + h := &MenuHandlers{ + userdataStore: store, + flagManager: fm, + accountService: mockAccountService, + st: mockState, + } + + tests := []struct { + name string + input []byte + temporarypin string + expectedResult resource.Result + }{ + { + name: "Test with correct pin confirmation", + input: []byte("1234"), + temporarypin: "1234", + expectedResult: resource.Result{ + FlagReset: []uint32{flag_pin_mismatch, flag_account_pin_reset}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Hash the PIN + hashedPIN, err := pin.HashPIN(tt.temporarypin) + if err != nil { + logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err) + t.Fatal(err) + } + + // Set up the expected behavior of the mock + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) + if err != nil { + t.Fatal(err) + } + + //Call the function under test + res, _ := h.ConfirmPinChange(ctx, "confirm_pin_change", tt.input) + + //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") + + }) + } +} + +func TestValidateBlockedNumber(t *testing.T) { + sessionId := "session123" + validNumber := "+254712345678" + invalidNumber := "12343" // Invalid phone number + unregisteredNumber := "+254734567890" // Valid but unregistered number + publicKey := "0X13242618721" + mockState := state.NewState(128) + + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Fatal(err) + } + flag_unregistered_number, _ := fm.GetFlag("flag_unregistered_number") + + h := &MenuHandlers{ + userdataStore: userStore, + st: mockState, + flagManager: fm, + } + + err = userStore.WriteEntry(ctx, validNumber, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + input []byte + expectedResult resource.Result + }{ + { + name: "Valid and registered number", + input: []byte(validNumber), + expectedResult: resource.Result{}, + }, + { + name: "Invalid Phone Number", + input: []byte(invalidNumber), + expectedResult: resource.Result{ + FlagSet: []uint32{flag_unregistered_number}, + }, + }, + { + name: "Unregistered Phone Number", + input: []byte(unregisteredNumber), + expectedResult: resource.Result{ + FlagSet: []uint32{flag_unregistered_number}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := h.ValidateBlockedNumber(ctx, "validate_blocked_number", tt.input) + + assert.NoError(t, err) + + assert.Equal(t, tt.expectedResult, res) + + if tt.name == "Valid and registered number" { + blockedNumber, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, validNumber, string(blockedNumber)) + } + }) + } +} + +func TestResetOthersPin(t *testing.T) { + sessionId := "session123" + blockedNumber := "+254712345678" + testPin := "1234" + + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + hashedPIN, err := pin.HashPIN(testPin) + if err != nil { + logg.ErrorCtxf(ctx, "failed to hash testPin", "error", err) + t.Fatal(err) + } + + h := &MenuHandlers{ + userdataStore: userStore, + } + + // Write initial data to the store + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(blockedNumber)) + if err != nil { + t.Fatal(err) + } + err = userStore.WriteEntry(ctx, blockedNumber, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) + if err != nil { + t.Fatal(err) + } + + _, err = h.ResetOthersPin(ctx, "reset_others_pin", []byte("")) + + assert.NoError(t, err) +} -- 2.45.2 From baa7526df3c2808f5916dbd735a2050b70361b89 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 17:07:26 +0300 Subject: [PATCH 17/26] move account_status tests --- handlers/application/account_status_test.go | 135 ++++++++++++++++++++ handlers/application/menuhandler_test.go | 123 ------------------ 2 files changed, 135 insertions(+), 123 deletions(-) create mode 100644 handlers/application/account_status_test.go diff --git a/handlers/application/account_status_test.go b/handlers/application/account_status_test.go new file mode 100644 index 0000000..fc7e09c --- /dev/null +++ b/handlers/application/account_status_test.go @@ -0,0 +1,135 @@ +package application + +import ( + "context" + "testing" + + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/sarafu-api/models" + "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/alecthomas/assert/v2" +) + +func TestCheckAccountStatus(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + flag_account_success, _ := fm.GetFlag("flag_account_success") + flag_account_pending, _ := fm.GetFlag("flag_account_pending") + flag_api_error, _ := fm.GetFlag("flag_api_call_error") + + tests := []struct { + name string + publicKey []byte + response *models.TrackStatusResult + expectedResult resource.Result + }{ + { + name: "Test when account is on the Sarafu network", + publicKey: []byte("TrackingId1234"), + response: &models.TrackStatusResult{ + Active: true, + }, + expectedResult: resource.Result{ + FlagSet: []uint32{flag_account_success}, + FlagReset: []uint32{flag_api_error, flag_account_pending}, + }, + }, + { + name: "Test when the account is not yet on the sarafu network", + publicKey: []byte("TrackingId1234"), + response: &models.TrackStatusResult{ + Active: false, + }, + expectedResult: resource.Result{ + FlagSet: []uint32{flag_account_pending}, + FlagReset: []uint32{flag_api_error, flag_account_success}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockAccountService := new(mocks.MockAccountService) + + h := &MenuHandlers{ + userdataStore: store, + accountService: mockAccountService, + flagManager: fm, + } + + err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(tt.publicKey)) + if err != nil { + t.Fatal(err) + } + + mockAccountService.On("TrackAccountStatus", string(tt.publicKey)).Return(tt.response, nil) + + // Call the method under test + res, _ := h.CheckAccountStatus(ctx, "check_account_status", []byte("")) + + // Assert that 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") + }) + } +} + +func TestCheckBlockedStatus(t *testing.T) { + ctx, store := InitializeTestStore(t) + sessionId := "session123" + ctx = context.WithValue(ctx, "SessionId", sessionId) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + flag_account_blocked, _ := fm.GetFlag("flag_account_blocked") + flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset") + + h := &MenuHandlers{ + userdataStore: store, + flagManager: fm, + } + + tests := []struct { + name string + currentWrongPinAttempts string + expectedResult resource.Result + }{ + { + name: "Currently blocked account", + currentWrongPinAttempts: "4", + expectedResult: resource.Result{ + FlagReset: []uint32{flag_account_pin_reset}, + }, + }, + { + name: "Account with 0 wrong PIN attempts", + currentWrongPinAttempts: "0", + expectedResult: resource.Result{ + FlagReset: []uint32{flag_account_pin_reset, flag_account_blocked}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(tt.currentWrongPinAttempts)); err != nil { + t.Fatal(err) + } + + res, err := h.CheckBlockedStatus(ctx, "", []byte("")) + + assert.NoError(t, err) + assert.Equal(t, tt.expectedResult, res) + }) + } +} diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index 83c23ce..c13ba9c 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -1256,77 +1256,6 @@ func TestVerifyCreatePin(t *testing.T) { } } -func TestCheckAccountStatus(t *testing.T) { - sessionId := "session123" - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - flag_account_success, _ := fm.GetFlag("flag_account_success") - flag_account_pending, _ := fm.GetFlag("flag_account_pending") - flag_api_error, _ := fm.GetFlag("flag_api_call_error") - - tests := []struct { - name string - publicKey []byte - response *models.TrackStatusResult - expectedResult resource.Result - }{ - { - name: "Test when account is on the Sarafu network", - publicKey: []byte("TrackingId1234"), - response: &models.TrackStatusResult{ - Active: true, - }, - expectedResult: resource.Result{ - FlagSet: []uint32{flag_account_success}, - FlagReset: []uint32{flag_api_error, flag_account_pending}, - }, - }, - { - name: "Test when the account is not yet on the sarafu network", - publicKey: []byte("TrackingId1234"), - response: &models.TrackStatusResult{ - Active: false, - }, - expectedResult: resource.Result{ - FlagSet: []uint32{flag_account_pending}, - FlagReset: []uint32{flag_api_error, flag_account_success}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockAccountService := new(mocks.MockAccountService) - - h := &MenuHandlers{ - userdataStore: store, - accountService: mockAccountService, - flagManager: fm, - } - - err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(tt.publicKey)) - if err != nil { - t.Fatal(err) - } - - mockAccountService.On("TrackAccountStatus", string(tt.publicKey)).Return(tt.response, nil) - - // Call the method under test - res, _ := h.CheckAccountStatus(ctx, "check_account_status", []byte("")) - - // Assert that 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") - }) - } -} - func TestTransactionReset(t *testing.T) { sessionId := "session123" ctx, store := InitializeTestStore(t) @@ -2290,58 +2219,6 @@ func TestPersistLanguageCode(t *testing.T) { } } -func TestCheckBlockedStatus(t *testing.T) { - ctx, store := InitializeTestStore(t) - sessionId := "session123" - ctx = context.WithValue(ctx, "SessionId", sessionId) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - flag_account_blocked, _ := fm.GetFlag("flag_account_blocked") - flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset") - - h := &MenuHandlers{ - userdataStore: store, - flagManager: fm, - } - - tests := []struct { - name string - currentWrongPinAttempts string - expectedResult resource.Result - }{ - { - name: "Currently blocked account", - currentWrongPinAttempts: "4", - expectedResult: resource.Result{ - FlagReset: []uint32{flag_account_pin_reset}, - }, - }, - { - name: "Account with 0 wrong PIN attempts", - currentWrongPinAttempts: "0", - expectedResult: resource.Result{ - FlagReset: []uint32{flag_account_pin_reset, flag_account_blocked}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(tt.currentWrongPinAttempts)); err != nil { - t.Fatal(err) - } - - res, err := h.CheckBlockedStatus(ctx, "", []byte("")) - - assert.NoError(t, err) - assert.Equal(t, tt.expectedResult, res) - }) - } -} - func TestPersistInitialLanguageCode(t *testing.T) { ctx, store := InitializeTestStore(t) -- 2.45.2 From 57b0fcb55f393dc75d7b0378d4e42529a9485b38 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 17:13:51 +0300 Subject: [PATCH 18/26] move authorization tests --- handlers/application/authorization_test.go | 181 +++++++++++++++++++++ handlers/application/menuhandler_test.go | 167 ------------------- 2 files changed, 181 insertions(+), 167 deletions(-) create mode 100644 handlers/application/authorization_test.go diff --git a/handlers/application/authorization_test.go b/handlers/application/authorization_test.go new file mode 100644 index 0000000..8fc0aa5 --- /dev/null +++ b/handlers/application/authorization_test.go @@ -0,0 +1,181 @@ +package application + +import ( + "context" + "log" + "testing" + + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/state" + "git.grassecon.net/grassrootseconomics/common/pin" + "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/alecthomas/assert/v2" +) + +func TestAuthorize(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + + // Create required mocks + mockAccountService := new(mocks.MockAccountService) + mockState := state.NewState(16) + flag_incorrect_pin, _ := fm.GetFlag("flag_incorrect_pin") + flag_account_authorized, _ := fm.GetFlag("flag_account_authorized") + flag_allow_update, _ := fm.GetFlag("flag_allow_update") + + // Set 1234 is the correct account pin + accountPIN := "1234" + + h := &MenuHandlers{ + userdataStore: store, + accountService: mockAccountService, + flagManager: fm, + st: mockState, + } + + tests := []struct { + name string + input []byte + expectedResult resource.Result + }{ + { + name: "Test with correct PIN", + input: []byte("1234"), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_incorrect_pin}, + FlagSet: []uint32{flag_allow_update, flag_account_authorized}, + }, + }, + { + name: "Test with incorrect PIN", + input: []byte("1235"), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_account_authorized, flag_allow_update}, + FlagSet: []uint32{flag_incorrect_pin}, + }, + }, + { + name: "Test with PIN that is not a 4 digit", + input: []byte("1235aqds"), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_account_authorized, flag_allow_update}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Hash the PIN + hashedPIN, err := pin.HashPIN(accountPIN) + if err != nil { + logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err) + t.Fatal(err) + } + + err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedPIN)) + if err != nil { + t.Fatal(err) + } + + // Call the method under test + res, err := h.Authorize(ctx, "authorize", []byte(tt.input)) + + // Assert that 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") + }) + } +} + +func TestResetAllowUpdate(t *testing.T) { + fm, err := NewFlagManager(flagsPath) + if err != nil { + log.Fatal(err) + } + + flag_allow_update, _ := fm.GetFlag("flag_allow_update") + + // Define test cases + tests := []struct { + name string + input []byte + expectedResult resource.Result + }{ + { + name: "Resets allow update", + input: []byte(""), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_allow_update}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create the MenuHandlers instance with the mock flag manager + h := &MenuHandlers{ + flagManager: fm, + } + + // Call the method + res, err := h.ResetAllowUpdate(context.Background(), "reset_allow update", tt.input) + if err != nil { + t.Error(err) + } + + // Assert that the Result FlagSet has the required flags after language switch + assert.Equal(t, res, tt.expectedResult, "Flags should be equal to account created") + }) + } +} + +func TestResetAccountAuthorized(t *testing.T) { + fm, err := NewFlagManager(flagsPath) + if err != nil { + log.Fatal(err) + } + + flag_account_authorized, _ := fm.GetFlag("flag_account_authorized") + + // Define test cases + tests := []struct { + name string + input []byte + expectedResult resource.Result + }{ + { + name: "Resets account authorized", + input: []byte(""), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_account_authorized}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create the MenuHandlers instance with the mock flag manager + h := &MenuHandlers{ + flagManager: fm, + } + + // Call the method + res, err := h.ResetAccountAuthorized(context.Background(), "reset_account_authorized", tt.input) + if err != nil { + t.Error(err) + } + + // Assert that the Result FlagSet has the required flags after language switch + assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset") + }) + } +} diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index c13ba9c..85ffe03 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -829,90 +829,6 @@ func TestSetLanguage(t *testing.T) { } } -func TestResetAllowUpdate(t *testing.T) { - fm, err := NewFlagManager(flagsPath) - if err != nil { - log.Fatal(err) - } - - flag_allow_update, _ := fm.GetFlag("flag_allow_update") - - // Define test cases - tests := []struct { - name string - input []byte - expectedResult resource.Result - }{ - { - name: "Resets allow update", - input: []byte(""), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_allow_update}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create the MenuHandlers instance with the mock flag manager - h := &MenuHandlers{ - flagManager: fm, - } - - // Call the method - res, err := h.ResetAllowUpdate(context.Background(), "reset_allow update", tt.input) - if err != nil { - t.Error(err) - } - - // Assert that the Result FlagSet has the required flags after language switch - assert.Equal(t, res, tt.expectedResult, "Flags should be equal to account created") - }) - } -} - -func TestResetAccountAuthorized(t *testing.T) { - fm, err := NewFlagManager(flagsPath) - if err != nil { - log.Fatal(err) - } - - flag_account_authorized, _ := fm.GetFlag("flag_account_authorized") - - // Define test cases - tests := []struct { - name string - input []byte - expectedResult resource.Result - }{ - { - name: "Resets account authorized", - input: []byte(""), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_account_authorized}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create the MenuHandlers instance with the mock flag manager - h := &MenuHandlers{ - flagManager: fm, - } - - // Call the method - res, err := h.ResetAccountAuthorized(context.Background(), "reset_account_authorized", tt.input) - if err != nil { - t.Error(err) - } - - // Assert that the Result FlagSet has the required flags after language switch - assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset") - }) - } -} - func TestIncorrectPinReset(t *testing.T) { sessionId := "session123" ctx, store := InitializeTestStore(t) @@ -1039,89 +955,6 @@ func TestResetIncorrectYob(t *testing.T) { } } -func TestAuthorize(t *testing.T) { - sessionId := "session123" - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - - // Create required mocks - mockAccountService := new(mocks.MockAccountService) - mockState := state.NewState(16) - flag_incorrect_pin, _ := fm.GetFlag("flag_incorrect_pin") - flag_account_authorized, _ := fm.GetFlag("flag_account_authorized") - flag_allow_update, _ := fm.GetFlag("flag_allow_update") - - // Set 1234 is the correct account pin - accountPIN := "1234" - - h := &MenuHandlers{ - userdataStore: store, - accountService: mockAccountService, - flagManager: fm, - st: mockState, - } - - tests := []struct { - name string - input []byte - expectedResult resource.Result - }{ - { - name: "Test with correct PIN", - input: []byte("1234"), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_incorrect_pin}, - FlagSet: []uint32{flag_allow_update, flag_account_authorized}, - }, - }, - { - name: "Test with incorrect PIN", - input: []byte("1235"), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_account_authorized, flag_allow_update}, - FlagSet: []uint32{flag_incorrect_pin}, - }, - }, - { - name: "Test with PIN that is not a 4 digit", - input: []byte("1235aqds"), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_account_authorized, flag_allow_update}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Hash the PIN - hashedPIN, err := pin.HashPIN(accountPIN) - if err != nil { - logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err) - t.Fatal(err) - } - - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedPIN)) - if err != nil { - t.Fatal(err) - } - - // Call the method under test - res, err := h.Authorize(ctx, "authorize", []byte(tt.input)) - - // Assert that 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") - }) - } -} - func TestVerifyYob(t *testing.T) { fm, err := NewFlagManager(flagsPath) if err != nil { -- 2.45.2 From 916026985f0e5bac647316523de9ef7c957f03be Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 17:16:44 +0300 Subject: [PATCH 19/26] move balance tests --- handlers/application/balance_test.go | 146 +++++++++++++++++++++++ handlers/application/menuhandler_test.go | 134 --------------------- 2 files changed, 146 insertions(+), 134 deletions(-) create mode 100644 handlers/application/balance_test.go diff --git a/handlers/application/balance_test.go b/handlers/application/balance_test.go new file mode 100644 index 0000000..c8da2ea --- /dev/null +++ b/handlers/application/balance_test.go @@ -0,0 +1,146 @@ +package application + +import ( + "context" + "testing" + + "git.defalsify.org/vise.git/lang" + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/state" + "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/alecthomas/assert/v2" +) + +func TestCheckBalance(t *testing.T) { + ctx, store := InitializeTestStore(t) + + tests := []struct { + name string + sessionId string + publicKey string + alias string + activeSym string + activeBal string + expectedResult resource.Result + expectError bool + }{ + { + name: "User with no active sym", + sessionId: "session123", + publicKey: "0X98765432109", + alias: "", + activeSym: "", + activeBal: "", + expectedResult: resource.Result{Content: "Balance: 0.00 \n"}, + expectError: false, + }, + { + name: "User with active sym", + sessionId: "session123", + publicKey: "0X98765432109", + alias: "", + activeSym: "ETH", + activeBal: "1.5", + expectedResult: resource.Result{Content: "Balance: 1.50 ETH\n"}, + expectError: false, + }, + { + name: "User with active sym and alias", + sessionId: "session123", + publicKey: "0X98765432109", + alias: "user72", + activeSym: "SRF", + activeBal: "10.967", + expectedResult: resource.Result{Content: "user72 balance: 10.96 SRF\n"}, + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockAccountService := new(mocks.MockAccountService) + ctx := context.WithValue(ctx, "SessionId", tt.sessionId) + + h := &MenuHandlers{ + userdataStore: store, + accountService: mockAccountService, + } + + if tt.alias != "" { + err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(tt.alias)) + if err != nil { + t.Fatal(err) + } + } + + if tt.activeSym != "" { + err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.activeSym)) + if err != nil { + t.Fatal(err) + } + } + + if tt.activeBal != "" { + err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal)) + if err != nil { + t.Fatal(err) + } + } + + 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") + } + + mockAccountService.AssertExpectations(t) + }) + } +} + +func TestFetchCommunityBalance(t *testing.T) { + // Define test data + sessionId := "session123" + ctx, store := InitializeTestStore(t) + + tests := []struct { + name string + languageCode string + expectedResult resource.Result + }{ + { + name: "Test community balance content when language is english", + expectedResult: resource.Result{ + Content: "Community Balance: 0.00", + }, + languageCode: "eng", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + mockAccountService := new(mocks.MockAccountService) + mockState := state.NewState(16) + + h := &MenuHandlers{ + userdataStore: store, + st: mockState, + accountService: mockAccountService, + } + ctx = context.WithValue(ctx, "SessionId", sessionId) + ctx = context.WithValue(ctx, "Language", lang.Language{ + Code: tt.languageCode, + }) + + // Call the method + res, _ := h.FetchCommunityBalance(ctx, "fetch_community_balance", []byte("")) + + //Assert that the result set to content is what was expected + assert.Equal(t, res, tt.expectedResult, "Result should match expected result") + }) + } +} diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index 85ffe03..dba9749 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -1534,96 +1534,6 @@ func TestValidateRecipient(t *testing.T) { } } -func TestCheckBalance(t *testing.T) { - ctx, store := InitializeTestStore(t) - - tests := []struct { - name string - sessionId string - publicKey string - alias string - activeSym string - activeBal string - expectedResult resource.Result - expectError bool - }{ - { - name: "User with no active sym", - sessionId: "session123", - publicKey: "0X98765432109", - alias: "", - activeSym: "", - activeBal: "", - expectedResult: resource.Result{Content: "Balance: 0.00 \n"}, - expectError: false, - }, - { - name: "User with active sym", - sessionId: "session123", - publicKey: "0X98765432109", - alias: "", - activeSym: "ETH", - activeBal: "1.5", - expectedResult: resource.Result{Content: "Balance: 1.50 ETH\n"}, - expectError: false, - }, - { - name: "User with active sym and alias", - sessionId: "session123", - publicKey: "0X98765432109", - alias: "user72", - activeSym: "SRF", - activeBal: "10.967", - expectedResult: resource.Result{Content: "user72 balance: 10.96 SRF\n"}, - expectError: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockAccountService := new(mocks.MockAccountService) - ctx := context.WithValue(ctx, "SessionId", tt.sessionId) - - h := &MenuHandlers{ - userdataStore: store, - accountService: mockAccountService, - } - - if tt.alias != "" { - err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(tt.alias)) - if err != nil { - t.Fatal(err) - } - } - - if tt.activeSym != "" { - err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.activeSym)) - if err != nil { - t.Fatal(err) - } - } - - if tt.activeBal != "" { - err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal)) - if err != nil { - t.Fatal(err) - } - } - - 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") - } - - mockAccountService.AssertExpectations(t) - }) - } -} - func TestGetProfile(t *testing.T) { sessionId := "session123" ctx, store := InitializeTestStore(t) @@ -1702,50 +1612,6 @@ func TestGetProfile(t *testing.T) { } } -func TestFetchCommunityBalance(t *testing.T) { - - // Define test data - sessionId := "session123" - ctx, store := InitializeTestStore(t) - - tests := []struct { - name string - languageCode string - expectedResult resource.Result - }{ - { - name: "Test community balance content when language is english", - expectedResult: resource.Result{ - Content: "Community Balance: 0.00", - }, - languageCode: "eng", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - mockAccountService := new(mocks.MockAccountService) - mockState := state.NewState(16) - - h := &MenuHandlers{ - userdataStore: store, - st: mockState, - accountService: mockAccountService, - } - ctx = context.WithValue(ctx, "SessionId", sessionId) - ctx = context.WithValue(ctx, "Language", lang.Language{ - Code: tt.languageCode, - }) - - // Call the method - res, _ := h.FetchCommunityBalance(ctx, "fetch_community_balance", []byte("")) - - //Assert that the result set to content is what was expected - assert.Equal(t, res, tt.expectedResult, "Result should match expected result") - }) - } -} - func TestManageVouchers(t *testing.T) { sessionId := "session123" publicKey := "0X13242618721" -- 2.45.2 From 7c21b783325eaa17ad68a10a5d775210098efd3a Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 17:22:06 +0300 Subject: [PATCH 20/26] move language tests --- handlers/application/language_test.go | 159 +++++++++++++++++++++++ handlers/application/menuhandler_test.go | 147 --------------------- 2 files changed, 159 insertions(+), 147 deletions(-) create mode 100644 handlers/application/language_test.go diff --git a/handlers/application/language_test.go b/handlers/application/language_test.go new file mode 100644 index 0000000..b787c6b --- /dev/null +++ b/handlers/application/language_test.go @@ -0,0 +1,159 @@ +package application + +import ( + "context" + "log" + "testing" + + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/state" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/alecthomas/assert/v2" +) + +func TestSetLanguage(t *testing.T) { + fm, err := NewFlagManager(flagsPath) + if err != nil { + log.Fatal(err) + } + + sessionId := "session123" + ctx, store := InitializeTestStore(t) + + ctx = context.WithValue(ctx, "SessionId", sessionId) + + // Define test cases + tests := []struct { + name string + execPath []string + expectedResult resource.Result + }{ + { + name: "Set Default Language (English)", + execPath: []string{"set_eng"}, + expectedResult: resource.Result{ + FlagSet: []uint32{state.FLAG_LANG, 8}, + Content: "eng", + }, + }, + { + name: "Set Swahili Language", + execPath: []string{"set_swa"}, + expectedResult: resource.Result{ + FlagSet: []uint32{state.FLAG_LANG, 8}, + Content: "swa", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockState := state.NewState(16) + // Set the ExecPath + mockState.ExecPath = tt.execPath + + // Create the MenuHandlers instance with the mock flag manager + h := &MenuHandlers{ + flagManager: fm, + userdataStore: store, + st: mockState, + } + + // Call the method + res, err := h.SetLanguage(ctx, "set_language", nil) + if err != nil { + t.Error(err) + } + + // Assert that the Result FlagSet has the required flags after language switch + assert.Equal(t, res, tt.expectedResult, "Result should match expected result") + code, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE) + if err != nil { + t.Error(err) + } + + assert.Equal(t, string(code), tt.expectedResult.Content) + code, err = store.ReadEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE) + if err != nil { + t.Error(err) + } + assert.Equal(t, string(code), "eng") + }) + } +} + +func TestPersistLanguageCode(t *testing.T) { + ctx, store := InitializeTestStore(t) + + sessionId := "session123" + ctx = context.WithValue(ctx, "SessionId", sessionId) + + h := &MenuHandlers{ + userdataStore: store, + } + tests := []struct { + name string + code string + expectedLanguageCode string + }{ + { + name: "Set Default Language (English)", + code: "eng", + expectedLanguageCode: "eng", + }, + { + name: "Set Swahili Language", + code: "swa", + expectedLanguageCode: "swa", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := h.persistLanguageCode(ctx, test.code) + if err != nil { + t.Logf(err.Error()) + } + code, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE) + + assert.Equal(t, test.expectedLanguageCode, string(code)) + }) + } +} + +func TestPersistInitialLanguageCode(t *testing.T) { + ctx, store := InitializeTestStore(t) + + h := &MenuHandlers{ + userdataStore: store, + } + + tests := []struct { + name string + code string + sessionId string + }{ + { + name: "Persist initial Language (English)", + code: "eng", + sessionId: "session123", + }, + { + name: "Persist initial Language (Swahili)", + code: "swa", + sessionId: "session456", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := h.persistInitialLanguageCode(ctx, tt.sessionId, tt.code) + if err != nil { + t.Logf(err.Error()) + } + code, err := store.ReadEntry(ctx, tt.sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE) + + assert.Equal(t, tt.code, string(code)) + }) + } +} diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index dba9749..6a66178 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -758,77 +758,6 @@ func TestGetFlag(t *testing.T) { assert.Equal(t, uint32(flag), expectedFlag, "Flags should be equal to account created") } -func TestSetLanguage(t *testing.T) { - fm, err := NewFlagManager(flagsPath) - if err != nil { - log.Fatal(err) - } - - sessionId := "session123" - ctx, store := InitializeTestStore(t) - - ctx = context.WithValue(ctx, "SessionId", sessionId) - - // Define test cases - tests := []struct { - name string - execPath []string - expectedResult resource.Result - }{ - { - name: "Set Default Language (English)", - execPath: []string{"set_eng"}, - expectedResult: resource.Result{ - FlagSet: []uint32{state.FLAG_LANG, 8}, - Content: "eng", - }, - }, - { - name: "Set Swahili Language", - execPath: []string{"set_swa"}, - expectedResult: resource.Result{ - FlagSet: []uint32{state.FLAG_LANG, 8}, - Content: "swa", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockState := state.NewState(16) - // Set the ExecPath - mockState.ExecPath = tt.execPath - - // Create the MenuHandlers instance with the mock flag manager - h := &MenuHandlers{ - flagManager: fm, - userdataStore: store, - st: mockState, - } - - // Call the method - res, err := h.SetLanguage(ctx, "set_language", nil) - if err != nil { - t.Error(err) - } - - // Assert that the Result FlagSet has the required flags after language switch - assert.Equal(t, res, tt.expectedResult, "Result should match expected result") - code, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE) - if err != nil { - t.Error(err) - } - - assert.Equal(t, string(code), tt.expectedResult.Content) - code, err = store.ReadEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE) - if err != nil { - t.Error(err) - } - assert.Equal(t, string(code), "eng") - }) - } -} - func TestIncorrectPinReset(t *testing.T) { sessionId := "session123" ctx, store := InitializeTestStore(t) @@ -1879,82 +1808,6 @@ func TestGetVoucherDetails(t *testing.T) { assert.Equal(t, expectedResult, res) } -func TestPersistLanguageCode(t *testing.T) { - ctx, store := InitializeTestStore(t) - - sessionId := "session123" - ctx = context.WithValue(ctx, "SessionId", sessionId) - - h := &MenuHandlers{ - userdataStore: store, - } - tests := []struct { - name string - code string - expectedLanguageCode string - }{ - { - name: "Set Default Language (English)", - code: "eng", - expectedLanguageCode: "eng", - }, - { - name: "Set Swahili Language", - code: "swa", - expectedLanguageCode: "swa", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - err := h.persistLanguageCode(ctx, test.code) - if err != nil { - t.Logf(err.Error()) - } - code, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE) - - assert.Equal(t, test.expectedLanguageCode, string(code)) - }) - } -} - -func TestPersistInitialLanguageCode(t *testing.T) { - ctx, store := InitializeTestStore(t) - - h := &MenuHandlers{ - userdataStore: store, - } - - tests := []struct { - name string - code string - sessionId string - }{ - { - name: "Persist initial Language (English)", - code: "eng", - sessionId: "session123", - }, - { - name: "Persist initial Language (Swahili)", - code: "swa", - sessionId: "session456", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := h.persistInitialLanguageCode(ctx, tt.sessionId, tt.code) - if err != nil { - t.Logf(err.Error()) - } - code, err := store.ReadEntry(ctx, tt.sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE) - - assert.Equal(t, tt.code, string(code)) - }) - } -} - func TestCheckTransactions(t *testing.T) { mockAccountService := new(mocks.MockAccountService) sessionId := "session123" -- 2.45.2 From 5d38359b6627a17f5d1f950634d967aacaff46d2 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 17:31:05 +0300 Subject: [PATCH 21/26] move profile tests --- handlers/application/menuhandler_test.go | 750 ---------------------- handlers/application/profile_test.go | 766 +++++++++++++++++++++++ 2 files changed, 766 insertions(+), 750 deletions(-) create mode 100644 handlers/application/profile_test.go diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index 6a66178..a2fce4c 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -11,7 +11,6 @@ import ( "time" "git.defalsify.org/vise.git/cache" - "git.defalsify.org/vise.git/lang" "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/state" @@ -19,7 +18,6 @@ import ( "git.grassecon.net/grassrootseconomics/sarafu-api/models" "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks" "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/testservice" - "git.grassecon.net/grassrootseconomics/sarafu-vise/profile" "git.grassecon.net/grassrootseconomics/sarafu-vise/store" storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" @@ -292,335 +290,6 @@ func TestWithPersister_PanicWhenAlreadySet(t *testing.T) { }, "Should panic when trying to set a persister again.") } -func TestSaveFirstname(t *testing.T) { - sessionId := "session123" - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - _, logdb := InitializeTestLogdbStore(t) - - logDb := store.LogDb{ - Db: logdb, - } - - fm, _ := NewFlagManager(flagsPath) - - flag_allow_update, _ := fm.GetFlag("flag_allow_update") - flag_firstname_set, _ := fm.GetFlag("flag_firstname_set") - - // Set the flag in the State - mockState := state.NewState(128) - mockState.SetFlag(flag_allow_update) - - expectedResult := resource.Result{} - - // Define test data - firstName := "John" - - if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil { - t.Fatal(err) - } - - expectedResult.FlagSet = []uint32{flag_firstname_set} - - // Create the MenuHandlers instance with the mock store - h := &MenuHandlers{ - userdataStore: userStore, - flagManager: fm, - st: mockState, - logDb: logDb, - } - - // Call the method - res, err := h.SaveFirstname(ctx, "save_firstname", []byte(firstName)) - - // Assert results - assert.NoError(t, err) - assert.Equal(t, expectedResult, res) - - // Verify that the DATA_FIRST_NAME entry has been updated with the temporary value - storedFirstName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME) - assert.Equal(t, firstName, string(storedFirstName)) -} - -func TestSaveFamilyname(t *testing.T) { - sessionId := "session123" - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - _, logdb := InitializeTestLogdbStore(t) - - logDb := store.LogDb{ - Db: logdb, - } - - fm, _ := NewFlagManager(flagsPath) - - flag_allow_update, _ := fm.GetFlag("flag_allow_update") - flag_firstname_set, _ := fm.GetFlag("flag_familyname_set") - - // Set the flag in the State - mockState := state.NewState(128) - mockState.SetFlag(flag_allow_update) - - expectedResult := resource.Result{} - - expectedResult.FlagSet = []uint32{flag_firstname_set} - - // Define test data - familyName := "Doeee" - - if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil { - t.Fatal(err) - } - - // Create the MenuHandlers instance with the mock store - h := &MenuHandlers{ - userdataStore: userStore, - st: mockState, - flagManager: fm, - logDb: logDb, - } - - // Call the method - res, err := h.SaveFamilyname(ctx, "save_familyname", []byte(familyName)) - - // Assert results - assert.NoError(t, err) - assert.Equal(t, expectedResult, res) - - // Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value - storedFamilyName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME) - assert.Equal(t, familyName, string(storedFamilyName)) -} - -func TestSaveYoB(t *testing.T) { - sessionId := "session123" - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - _, logdb := InitializeTestLogdbStore(t) - - logDb := store.LogDb{ - Db: logdb, - } - - fm, _ := NewFlagManager(flagsPath) - - flag_allow_update, _ := fm.GetFlag("flag_allow_update") - flag_yob_set, _ := fm.GetFlag("flag_yob_set") - - // Set the flag in the State - mockState := state.NewState(108) - mockState.SetFlag(flag_allow_update) - - expectedResult := resource.Result{} - - // Define test data - yob := "1980" - - if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil { - t.Fatal(err) - } - - expectedResult.FlagSet = []uint32{flag_yob_set} - - // Create the MenuHandlers instance with the mock store - h := &MenuHandlers{ - userdataStore: userStore, - flagManager: fm, - st: mockState, - logDb: logDb, - } - - // Call the method - res, err := h.SaveYob(ctx, "save_yob", []byte(yob)) - - // Assert results - assert.NoError(t, err) - assert.Equal(t, expectedResult, res) - - // Verify that the DATA_YOB entry has been updated with the temporary value - storedYob, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_YOB) - assert.Equal(t, yob, string(storedYob)) -} - -func TestSaveLocation(t *testing.T) { - sessionId := "session123" - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - _, logdb := InitializeTestLogdbStore(t) - - logDb := store.LogDb{ - Db: logdb, - } - - fm, _ := NewFlagManager(flagsPath) - - flag_allow_update, _ := fm.GetFlag("flag_allow_update") - flag_location_set, _ := fm.GetFlag("flag_location_set") - - // Set the flag in the State - mockState := state.NewState(108) - mockState.SetFlag(flag_allow_update) - - expectedResult := resource.Result{} - - // Define test data - location := "Kilifi" - - if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location)); err != nil { - t.Fatal(err) - } - - expectedResult.FlagSet = []uint32{flag_location_set} - - // Create the MenuHandlers instance with the mock store - h := &MenuHandlers{ - userdataStore: userStore, - flagManager: fm, - st: mockState, - logDb: logDb, - } - - // Call the method - res, err := h.SaveLocation(ctx, "save_location", []byte(location)) - - // Assert results - assert.NoError(t, err) - assert.Equal(t, expectedResult, res) - - // Verify that the DATA_LOCATION entry has been updated with the temporary value - storedLocation, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION) - assert.Equal(t, location, string(storedLocation)) -} - -func TestSaveOfferings(t *testing.T) { - sessionId := "session123" - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - _, logdb := InitializeTestLogdbStore(t) - - logDb := store.LogDb{ - Db: logdb, - } - - fm, _ := NewFlagManager(flagsPath) - - flag_allow_update, _ := fm.GetFlag("flag_allow_update") - flag_offerings_set, _ := fm.GetFlag("flag_offerings_set") - - // Set the flag in the State - mockState := state.NewState(108) - mockState.SetFlag(flag_allow_update) - - expectedResult := resource.Result{} - - // Define test data - offerings := "Bananas" - - if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil { - t.Fatal(err) - } - - expectedResult.FlagSet = []uint32{flag_offerings_set} - - // Create the MenuHandlers instance with the mock store - h := &MenuHandlers{ - userdataStore: userStore, - flagManager: fm, - st: mockState, - logDb: logDb, - } - - // Call the method - res, err := h.SaveOfferings(ctx, "save_offerings", []byte(offerings)) - - // Assert results - assert.NoError(t, err) - assert.Equal(t, expectedResult, res) - - // Verify that the DATA_OFFERINGS entry has been updated with the temporary value - storedOfferings, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS) - assert.Equal(t, offerings, string(storedOfferings)) -} - -func TestSaveGender(t *testing.T) { - sessionId := "session123" - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - _, logdb := InitializeTestLogdbStore(t) - - logDb := store.LogDb{ - Db: logdb, - } - - fm, _ := NewFlagManager(flagsPath) - - flag_allow_update, _ := fm.GetFlag("flag_allow_update") - flag_gender_set, _ := fm.GetFlag("flag_gender_set") - - // Set the flag in the State - mockState := state.NewState(108) - mockState.SetFlag(flag_allow_update) - - // Define test cases - tests := []struct { - name string - input []byte - expectedGender string - executingSymbol string - }{ - { - name: "Valid Male Input", - input: []byte("1"), - expectedGender: "male", - executingSymbol: "set_male", - }, - { - name: "Valid Female Input", - input: []byte("2"), - expectedGender: "female", - executingSymbol: "set_female", - }, - { - name: "Valid Unspecified Input", - input: []byte("3"), - executingSymbol: "set_unspecified", - expectedGender: "unspecified", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil { - t.Fatal(err) - } - - mockState.ExecPath = append(mockState.ExecPath, tt.executingSymbol) - // Create the MenuHandlers instance with the mock store - h := &MenuHandlers{ - userdataStore: userStore, - st: mockState, - flagManager: fm, - logDb: logDb, - } - - expectedResult := resource.Result{} - - // Call the method - res, err := h.SaveGender(ctx, "save_gender", tt.input) - - expectedResult.FlagSet = []uint32{flag_gender_set} - - // Assert results - assert.NoError(t, err) - assert.Equal(t, expectedResult, res) - - // Verify that the DATA_GENDER entry has been updated with the temporary value - storedGender, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_GENDER) - assert.Equal(t, tt.expectedGender, string(storedGender)) - }) - } -} - func TestCheckIdentifier(t *testing.T) { sessionId := "session123" ctx, userdatastore := InitializeTestStore(t) @@ -842,109 +511,6 @@ func TestIncorrectPinReset(t *testing.T) { } } -func TestResetIncorrectYob(t *testing.T) { - fm, err := NewFlagManager(flagsPath) - if err != nil { - log.Fatal(err) - } - - flag_incorrect_date_format, _ := fm.GetFlag("flag_incorrect_date_format") - - // Define test cases - tests := []struct { - name string - input []byte - expectedResult resource.Result - }{ - { - name: "Test incorrect yob reset", - input: []byte(""), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_incorrect_date_format}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create the MenuHandlers instance with the mock flag manager - h := &MenuHandlers{ - flagManager: fm, - } - - // Call the method - res, err := h.ResetIncorrectYob(context.Background(), "reset_incorrect_yob", tt.input) - if err != nil { - t.Error(err) - } - - // Assert that the Result FlagSet has the required flags after language switch - assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset") - }) - } -} - -func TestVerifyYob(t *testing.T) { - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - - sessionId := "session123" - // Create required mocks - mockAccountService := new(mocks.MockAccountService) - mockState := state.NewState(16) - flag_incorrect_date_format, _ := fm.GetFlag("flag_incorrect_date_format") - ctx := context.WithValue(context.Background(), "SessionId", sessionId) - - h := &MenuHandlers{ - accountService: mockAccountService, - flagManager: fm, - st: mockState, - } - - tests := []struct { - name string - input []byte - expectedResult resource.Result - }{ - { - name: "Test with correct yob", - input: []byte("1980"), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_incorrect_date_format}, - }, - }, - { - name: "Test with incorrect yob", - input: []byte("sgahaha"), - expectedResult: resource.Result{ - FlagSet: []uint32{flag_incorrect_date_format}, - }, - }, - { - name: "Test with numeric but less 4 digits", - input: []byte("123"), - expectedResult: resource.Result{ - FlagSet: []uint32{flag_incorrect_date_format}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Call the method under test - res, err := h.VerifyYob(ctx, "verify_yob", []byte(tt.input)) - - // Assert that 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") - }) - } -} - func TestVerifyCreatePin(t *testing.T) { sessionId := "session123" ctx, store := InitializeTestStore(t) @@ -1463,84 +1029,6 @@ func TestValidateRecipient(t *testing.T) { } } -func TestGetProfile(t *testing.T) { - sessionId := "session123" - ctx, store := InitializeTestStore(t) - - mockAccountService := new(mocks.MockAccountService) - mockState := state.NewState(16) - - h := &MenuHandlers{ - userdataStore: store, - accountService: mockAccountService, - st: mockState, - } - - tests := []struct { - name string - languageCode string - keys []storedb.DataTyp - profileInfo []string - result resource.Result - }{ - { - name: "Test with full profile information in eng", - keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS}, - profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"}, - languageCode: "eng", - result: resource.Result{ - Content: fmt.Sprintf( - "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n", - "John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn", - ), - }, - }, - { - name: "Test with with profile information in swa", - keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS}, - profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"}, - languageCode: "swa", - result: resource.Result{ - Content: fmt.Sprintf( - "Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\nLakabu yako: %s\n", - "John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn", - ), - }, - }, - { - name: "Test with with profile information with language that is not yet supported", - keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS}, - profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"}, - languageCode: "nor", - result: resource.Result{ - Content: fmt.Sprintf( - "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n", - "John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn", - ), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx = context.WithValue(ctx, "SessionId", sessionId) - ctx = context.WithValue(ctx, "Language", lang.Language{ - Code: tt.languageCode, - }) - for index, key := range tt.keys { - err := store.WriteEntry(ctx, sessionId, key, []byte(tt.profileInfo[index])) - if err != nil { - t.Fatal(err) - } - } - - res, _ := h.GetProfileInfo(ctx, "get_profile_info", []byte("")) - - //Assert that the result set to content is what was expected - assert.Equal(t, res, tt.result, "Result should contain profile information served back to user") - }) - } -} - func TestManageVouchers(t *testing.T) { sessionId := "session123" publicKey := "0X13242618721" @@ -2214,129 +1702,6 @@ func TestShowBlockedAccount(t *testing.T) { } } -func TestGetCurrentProfileInfo(t *testing.T) { - sessionId := "session123" - ctx, store := InitializeTestStore(t) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Fatal(err) - } - flag_firstname_set, _ := fm.GetFlag("flag_firstname_set") - flag_familyname_set, _ := fm.GetFlag("flag_familyname_set") - flag_yob_set, _ := fm.GetFlag("flag_yob_set") - flag_gender_set, _ := fm.GetFlag("flag_gender_set") - flag_location_set, _ := fm.GetFlag("flag_location_set") - flag_offerings_set, _ := fm.GetFlag("flag_offerings_set") - flag_back_set, _ := fm.GetFlag("flag_back_set") - - h := &MenuHandlers{ - userdataStore: store, - flagManager: fm, - st: state.NewState(16), - } - - tests := []struct { - name string - execPath string - dbKey storedb.DataTyp - value string - expected resource.Result - }{ - { - name: "Test fetching first name", - execPath: "edit_first_name", - dbKey: storedb.DATA_FIRST_NAME, - value: "John", - expected: resource.Result{ - FlagReset: []uint32{flag_back_set}, - FlagSet: []uint32{flag_firstname_set}, - Content: "John", - }, - }, - { - name: "Test fetching family name", - execPath: "edit_family_name", - dbKey: storedb.DATA_FAMILY_NAME, - value: "Doe", - expected: resource.Result{ - FlagReset: []uint32{flag_back_set}, - FlagSet: []uint32{flag_familyname_set}, - Content: "Doe", - }, - }, - { - name: "Test fetching year of birth", - execPath: "edit_yob", - dbKey: storedb.DATA_YOB, - value: "1980", - expected: resource.Result{ - FlagReset: []uint32{flag_back_set}, - FlagSet: []uint32{flag_yob_set}, - Content: "1980", - }, - }, - { - name: "Test fetching gender", - execPath: "edit_gender", - dbKey: storedb.DATA_GENDER, - value: "Male", - expected: resource.Result{ - FlagReset: []uint32{flag_back_set}, - FlagSet: []uint32{flag_gender_set}, - Content: "Male", - }, - }, - { - name: "Test fetching location", - execPath: "edit_location", - dbKey: storedb.DATA_LOCATION, - value: "Nairobi", - expected: resource.Result{ - FlagReset: []uint32{flag_back_set}, - FlagSet: []uint32{flag_location_set}, - Content: "Nairobi", - }, - }, - { - name: "Test fetching offerings", - execPath: "edit_offerings", - dbKey: storedb.DATA_OFFERINGS, - value: "Fruits", - expected: resource.Result{ - FlagReset: []uint32{flag_back_set}, - FlagSet: []uint32{flag_offerings_set}, - Content: "Fruits", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx = context.WithValue(ctx, "SessionId", sessionId) - ctx = context.WithValue(ctx, "Language", lang.Language{ - Code: "eng", - }) - // Set ExecPath to include tt.execPath - h.st.ExecPath = []string{tt.execPath} - - if tt.value != "" { - err := store.WriteEntry(ctx, sessionId, tt.dbKey, []byte(tt.value)) - if err != nil { - t.Fatal(err) - } - } - - res, err := h.GetCurrentProfileInfo(ctx, tt.execPath, []byte("")) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, tt.expected, res, "Result should match the expected output") - }) - } -} - func TestResetUnregisteredNumber(t *testing.T) { ctx := context.Background() @@ -2361,121 +1726,6 @@ func TestResetUnregisteredNumber(t *testing.T) { assert.Equal(t, expectedResult, res) } -func TestInsertProfileItems(t *testing.T) { - ctx, store := InitializeTestStore(t) - sessionId := "session123" - mockState := state.NewState(128) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Fatal(err) - } - - profileDataKeys := []storedb.DataTyp{ - storedb.DATA_FIRST_NAME, - storedb.DATA_FAMILY_NAME, - storedb.DATA_GENDER, - storedb.DATA_YOB, - storedb.DATA_LOCATION, - storedb.DATA_OFFERINGS, - } - - profileItems := []string{"John", "Doe", "Male", "1990", "Nairobi", "Software"} - - h := &MenuHandlers{ - userdataStore: store, - flagManager: fm, - st: mockState, - profile: &profile.Profile{ - ProfileItems: profileItems, - Max: 6, - }, - } - - res := &resource.Result{} - err = h.insertProfileItems(ctx, sessionId, res) - require.NoError(t, err) - - // Loop through profileDataKeys to validate stored values - for i, key := range profileDataKeys { - storedValue, err := store.ReadEntry(ctx, sessionId, key) - require.NoError(t, err) - assert.Equal(t, profileItems[i], string(storedValue)) - } -} - -func TestUpdateAllProfileItems(t *testing.T) { - ctx, store := InitializeTestStore(t) - sessionId := "session123" - publicKey := "0X13242618721" - - ctx = context.WithValue(ctx, "SessionId", sessionId) - - mockState := state.NewState(128) - mockAccountService := new(mocks.MockAccountService) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Fatal(err) - } - - flag_firstname_set, _ := fm.GetFlag("flag_firstname_set") - flag_familyname_set, _ := fm.GetFlag("flag_familyname_set") - flag_yob_set, _ := fm.GetFlag("flag_yob_set") - flag_gender_set, _ := fm.GetFlag("flag_gender_set") - flag_location_set, _ := fm.GetFlag("flag_location_set") - flag_offerings_set, _ := fm.GetFlag("flag_offerings_set") - - profileDataKeys := []storedb.DataTyp{ - storedb.DATA_FIRST_NAME, - storedb.DATA_FAMILY_NAME, - storedb.DATA_GENDER, - storedb.DATA_YOB, - storedb.DATA_LOCATION, - storedb.DATA_OFFERINGS, - } - - profileItems := []string{"John", "Doe", "Male", "1990", "Nairobi", "Software"} - - expectedResult := resource.Result{ - FlagSet: []uint32{ - flag_firstname_set, - flag_familyname_set, - flag_yob_set, - flag_gender_set, - flag_location_set, - flag_offerings_set, - }, - } - - h := &MenuHandlers{ - userdataStore: store, - flagManager: fm, - st: mockState, - accountService: mockAccountService, - profile: &profile.Profile{ - ProfileItems: profileItems, - Max: 6, - }, - } - - err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) - require.NoError(t, err) - - // Call the function under test - res, err := h.UpdateAllProfileItems(ctx, "symbol", nil) - assert.NoError(t, err) - - // Loop through profileDataKeys to validate stored values - for i, key := range profileDataKeys { - storedValue, err := store.ReadEntry(ctx, sessionId, key) - require.NoError(t, err) - assert.Equal(t, profileItems[i], string(storedValue)) - } - - assert.Equal(t, expectedResult, res) -} - func TestClearTemporaryValue(t *testing.T) { ctx, store := InitializeTestStore(t) sessionId := "session123" diff --git a/handlers/application/profile_test.go b/handlers/application/profile_test.go new file mode 100644 index 0000000..ecdef1c --- /dev/null +++ b/handlers/application/profile_test.go @@ -0,0 +1,766 @@ +package application + +import ( + "context" + "fmt" + "log" + "testing" + + "git.defalsify.org/vise.git/lang" + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/state" + "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks" + "git.grassecon.net/grassrootseconomics/sarafu-vise/profile" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/alecthomas/assert/v2" + "github.com/stretchr/testify/require" +) + +func TestSaveFirstname(t *testing.T) { + sessionId := "session123" + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } + + fm, _ := NewFlagManager(flagsPath) + + flag_allow_update, _ := fm.GetFlag("flag_allow_update") + flag_firstname_set, _ := fm.GetFlag("flag_firstname_set") + + // Set the flag in the State + mockState := state.NewState(128) + mockState.SetFlag(flag_allow_update) + + expectedResult := resource.Result{} + + // Define test data + firstName := "John" + + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil { + t.Fatal(err) + } + + expectedResult.FlagSet = []uint32{flag_firstname_set} + + // Create the MenuHandlers instance with the mock store + h := &MenuHandlers{ + userdataStore: userStore, + flagManager: fm, + st: mockState, + logDb: logDb, + } + + // Call the method + res, err := h.SaveFirstname(ctx, "save_firstname", []byte(firstName)) + + // Assert results + assert.NoError(t, err) + assert.Equal(t, expectedResult, res) + + // Verify that the DATA_FIRST_NAME entry has been updated with the temporary value + storedFirstName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME) + assert.Equal(t, firstName, string(storedFirstName)) +} + +func TestSaveFamilyname(t *testing.T) { + sessionId := "session123" + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } + + fm, _ := NewFlagManager(flagsPath) + + flag_allow_update, _ := fm.GetFlag("flag_allow_update") + flag_firstname_set, _ := fm.GetFlag("flag_familyname_set") + + // Set the flag in the State + mockState := state.NewState(128) + mockState.SetFlag(flag_allow_update) + + expectedResult := resource.Result{} + + expectedResult.FlagSet = []uint32{flag_firstname_set} + + // Define test data + familyName := "Doeee" + + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil { + t.Fatal(err) + } + + // Create the MenuHandlers instance with the mock store + h := &MenuHandlers{ + userdataStore: userStore, + st: mockState, + flagManager: fm, + logDb: logDb, + } + + // Call the method + res, err := h.SaveFamilyname(ctx, "save_familyname", []byte(familyName)) + + // Assert results + assert.NoError(t, err) + assert.Equal(t, expectedResult, res) + + // Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value + storedFamilyName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME) + assert.Equal(t, familyName, string(storedFamilyName)) +} + +func TestVerifyYob(t *testing.T) { + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + + sessionId := "session123" + // Create required mocks + mockAccountService := new(mocks.MockAccountService) + mockState := state.NewState(16) + flag_incorrect_date_format, _ := fm.GetFlag("flag_incorrect_date_format") + ctx := context.WithValue(context.Background(), "SessionId", sessionId) + + h := &MenuHandlers{ + accountService: mockAccountService, + flagManager: fm, + st: mockState, + } + + tests := []struct { + name string + input []byte + expectedResult resource.Result + }{ + { + name: "Test with correct yob", + input: []byte("1980"), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_incorrect_date_format}, + }, + }, + { + name: "Test with incorrect yob", + input: []byte("sgahaha"), + expectedResult: resource.Result{ + FlagSet: []uint32{flag_incorrect_date_format}, + }, + }, + { + name: "Test with numeric but less 4 digits", + input: []byte("123"), + expectedResult: resource.Result{ + FlagSet: []uint32{flag_incorrect_date_format}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Call the method under test + res, err := h.VerifyYob(ctx, "verify_yob", []byte(tt.input)) + + // Assert that 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") + }) + } +} + +func TestResetIncorrectYob(t *testing.T) { + fm, err := NewFlagManager(flagsPath) + if err != nil { + log.Fatal(err) + } + + flag_incorrect_date_format, _ := fm.GetFlag("flag_incorrect_date_format") + + // Define test cases + tests := []struct { + name string + input []byte + expectedResult resource.Result + }{ + { + name: "Test incorrect yob reset", + input: []byte(""), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_incorrect_date_format}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create the MenuHandlers instance with the mock flag manager + h := &MenuHandlers{ + flagManager: fm, + } + + // Call the method + res, err := h.ResetIncorrectYob(context.Background(), "reset_incorrect_yob", tt.input) + if err != nil { + t.Error(err) + } + + // Assert that the Result FlagSet has the required flags after language switch + assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset") + }) + } +} + +func TestSaveYob(t *testing.T) { + sessionId := "session123" + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } + + fm, _ := NewFlagManager(flagsPath) + + flag_allow_update, _ := fm.GetFlag("flag_allow_update") + flag_yob_set, _ := fm.GetFlag("flag_yob_set") + + // Set the flag in the State + mockState := state.NewState(108) + mockState.SetFlag(flag_allow_update) + + expectedResult := resource.Result{} + + // Define test data + yob := "1980" + + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil { + t.Fatal(err) + } + + expectedResult.FlagSet = []uint32{flag_yob_set} + + // Create the MenuHandlers instance with the mock store + h := &MenuHandlers{ + userdataStore: userStore, + flagManager: fm, + st: mockState, + logDb: logDb, + } + + // Call the method + res, err := h.SaveYob(ctx, "save_yob", []byte(yob)) + + // Assert results + assert.NoError(t, err) + assert.Equal(t, expectedResult, res) + + // Verify that the DATA_YOB entry has been updated with the temporary value + storedYob, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_YOB) + assert.Equal(t, yob, string(storedYob)) +} + +func TestSaveLocation(t *testing.T) { + sessionId := "session123" + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } + + fm, _ := NewFlagManager(flagsPath) + + flag_allow_update, _ := fm.GetFlag("flag_allow_update") + flag_location_set, _ := fm.GetFlag("flag_location_set") + + // Set the flag in the State + mockState := state.NewState(108) + mockState.SetFlag(flag_allow_update) + + expectedResult := resource.Result{} + + // Define test data + location := "Kilifi" + + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location)); err != nil { + t.Fatal(err) + } + + expectedResult.FlagSet = []uint32{flag_location_set} + + // Create the MenuHandlers instance with the mock store + h := &MenuHandlers{ + userdataStore: userStore, + flagManager: fm, + st: mockState, + logDb: logDb, + } + + // Call the method + res, err := h.SaveLocation(ctx, "save_location", []byte(location)) + + // Assert results + assert.NoError(t, err) + assert.Equal(t, expectedResult, res) + + // Verify that the DATA_LOCATION entry has been updated with the temporary value + storedLocation, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION) + assert.Equal(t, location, string(storedLocation)) +} + +func TestSaveGender(t *testing.T) { + sessionId := "session123" + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } + + fm, _ := NewFlagManager(flagsPath) + + flag_allow_update, _ := fm.GetFlag("flag_allow_update") + flag_gender_set, _ := fm.GetFlag("flag_gender_set") + + // Set the flag in the State + mockState := state.NewState(108) + mockState.SetFlag(flag_allow_update) + + // Define test cases + tests := []struct { + name string + input []byte + expectedGender string + executingSymbol string + }{ + { + name: "Valid Male Input", + input: []byte("1"), + expectedGender: "male", + executingSymbol: "set_male", + }, + { + name: "Valid Female Input", + input: []byte("2"), + expectedGender: "female", + executingSymbol: "set_female", + }, + { + name: "Valid Unspecified Input", + input: []byte("3"), + executingSymbol: "set_unspecified", + expectedGender: "unspecified", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil { + t.Fatal(err) + } + + mockState.ExecPath = append(mockState.ExecPath, tt.executingSymbol) + // Create the MenuHandlers instance with the mock store + h := &MenuHandlers{ + userdataStore: userStore, + st: mockState, + flagManager: fm, + logDb: logDb, + } + + expectedResult := resource.Result{} + + // Call the method + res, err := h.SaveGender(ctx, "save_gender", tt.input) + + expectedResult.FlagSet = []uint32{flag_gender_set} + + // Assert results + assert.NoError(t, err) + assert.Equal(t, expectedResult, res) + + // Verify that the DATA_GENDER entry has been updated with the temporary value + storedGender, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_GENDER) + assert.Equal(t, tt.expectedGender, string(storedGender)) + }) + } +} + +func TestSaveOfferings(t *testing.T) { + sessionId := "session123" + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } + + fm, _ := NewFlagManager(flagsPath) + + flag_allow_update, _ := fm.GetFlag("flag_allow_update") + flag_offerings_set, _ := fm.GetFlag("flag_offerings_set") + + // Set the flag in the State + mockState := state.NewState(108) + mockState.SetFlag(flag_allow_update) + + expectedResult := resource.Result{} + + // Define test data + offerings := "Bananas" + + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil { + t.Fatal(err) + } + + expectedResult.FlagSet = []uint32{flag_offerings_set} + + // Create the MenuHandlers instance with the mock store + h := &MenuHandlers{ + userdataStore: userStore, + flagManager: fm, + st: mockState, + logDb: logDb, + } + + // Call the method + res, err := h.SaveOfferings(ctx, "save_offerings", []byte(offerings)) + + // Assert results + assert.NoError(t, err) + assert.Equal(t, expectedResult, res) + + // Verify that the DATA_OFFERINGS entry has been updated with the temporary value + storedOfferings, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS) + assert.Equal(t, offerings, string(storedOfferings)) +} + +func TestGetCurrentProfileInfo(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestStore(t) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Fatal(err) + } + flag_firstname_set, _ := fm.GetFlag("flag_firstname_set") + flag_familyname_set, _ := fm.GetFlag("flag_familyname_set") + flag_yob_set, _ := fm.GetFlag("flag_yob_set") + flag_gender_set, _ := fm.GetFlag("flag_gender_set") + flag_location_set, _ := fm.GetFlag("flag_location_set") + flag_offerings_set, _ := fm.GetFlag("flag_offerings_set") + flag_back_set, _ := fm.GetFlag("flag_back_set") + + h := &MenuHandlers{ + userdataStore: store, + flagManager: fm, + st: state.NewState(16), + } + + tests := []struct { + name string + execPath string + dbKey storedb.DataTyp + value string + expected resource.Result + }{ + { + name: "Test fetching first name", + execPath: "edit_first_name", + dbKey: storedb.DATA_FIRST_NAME, + value: "John", + expected: resource.Result{ + FlagReset: []uint32{flag_back_set}, + FlagSet: []uint32{flag_firstname_set}, + Content: "John", + }, + }, + { + name: "Test fetching family name", + execPath: "edit_family_name", + dbKey: storedb.DATA_FAMILY_NAME, + value: "Doe", + expected: resource.Result{ + FlagReset: []uint32{flag_back_set}, + FlagSet: []uint32{flag_familyname_set}, + Content: "Doe", + }, + }, + { + name: "Test fetching year of birth", + execPath: "edit_yob", + dbKey: storedb.DATA_YOB, + value: "1980", + expected: resource.Result{ + FlagReset: []uint32{flag_back_set}, + FlagSet: []uint32{flag_yob_set}, + Content: "1980", + }, + }, + { + name: "Test fetching gender", + execPath: "edit_gender", + dbKey: storedb.DATA_GENDER, + value: "Male", + expected: resource.Result{ + FlagReset: []uint32{flag_back_set}, + FlagSet: []uint32{flag_gender_set}, + Content: "Male", + }, + }, + { + name: "Test fetching location", + execPath: "edit_location", + dbKey: storedb.DATA_LOCATION, + value: "Nairobi", + expected: resource.Result{ + FlagReset: []uint32{flag_back_set}, + FlagSet: []uint32{flag_location_set}, + Content: "Nairobi", + }, + }, + { + name: "Test fetching offerings", + execPath: "edit_offerings", + dbKey: storedb.DATA_OFFERINGS, + value: "Fruits", + expected: resource.Result{ + FlagReset: []uint32{flag_back_set}, + FlagSet: []uint32{flag_offerings_set}, + Content: "Fruits", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx = context.WithValue(ctx, "SessionId", sessionId) + ctx = context.WithValue(ctx, "Language", lang.Language{ + Code: "eng", + }) + // Set ExecPath to include tt.execPath + h.st.ExecPath = []string{tt.execPath} + + if tt.value != "" { + err := store.WriteEntry(ctx, sessionId, tt.dbKey, []byte(tt.value)) + if err != nil { + t.Fatal(err) + } + } + + res, err := h.GetCurrentProfileInfo(ctx, tt.execPath, []byte("")) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, tt.expected, res, "Result should match the expected output") + }) + } +} + +func TestGetProfileInfo(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestStore(t) + + mockAccountService := new(mocks.MockAccountService) + mockState := state.NewState(16) + + h := &MenuHandlers{ + userdataStore: store, + accountService: mockAccountService, + st: mockState, + } + + tests := []struct { + name string + languageCode string + keys []storedb.DataTyp + profileInfo []string + result resource.Result + }{ + { + name: "Test with full profile information in eng", + keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS}, + profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"}, + languageCode: "eng", + result: resource.Result{ + Content: fmt.Sprintf( + "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n", + "John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn", + ), + }, + }, + { + name: "Test with with profile information in swa", + keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS}, + profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"}, + languageCode: "swa", + result: resource.Result{ + Content: fmt.Sprintf( + "Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\nLakabu yako: %s\n", + "John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn", + ), + }, + }, + { + name: "Test with with profile information with language that is not yet supported", + keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS}, + profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"}, + languageCode: "nor", + result: resource.Result{ + Content: fmt.Sprintf( + "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n", + "John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn", + ), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx = context.WithValue(ctx, "SessionId", sessionId) + ctx = context.WithValue(ctx, "Language", lang.Language{ + Code: tt.languageCode, + }) + for index, key := range tt.keys { + err := store.WriteEntry(ctx, sessionId, key, []byte(tt.profileInfo[index])) + if err != nil { + t.Fatal(err) + } + } + + res, _ := h.GetProfileInfo(ctx, "get_profile_info", []byte("")) + + //Assert that the result set to content is what was expected + assert.Equal(t, res, tt.result, "Result should contain profile information served back to user") + }) + } +} + +func TestInsertProfileItems(t *testing.T) { + ctx, store := InitializeTestStore(t) + sessionId := "session123" + mockState := state.NewState(128) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Fatal(err) + } + + profileDataKeys := []storedb.DataTyp{ + storedb.DATA_FIRST_NAME, + storedb.DATA_FAMILY_NAME, + storedb.DATA_GENDER, + storedb.DATA_YOB, + storedb.DATA_LOCATION, + storedb.DATA_OFFERINGS, + } + + profileItems := []string{"John", "Doe", "Male", "1990", "Nairobi", "Software"} + + h := &MenuHandlers{ + userdataStore: store, + flagManager: fm, + st: mockState, + profile: &profile.Profile{ + ProfileItems: profileItems, + Max: 6, + }, + } + + res := &resource.Result{} + err = h.insertProfileItems(ctx, sessionId, res) + require.NoError(t, err) + + // Loop through profileDataKeys to validate stored values + for i, key := range profileDataKeys { + storedValue, err := store.ReadEntry(ctx, sessionId, key) + require.NoError(t, err) + assert.Equal(t, profileItems[i], string(storedValue)) + } +} + +func TestUpdateAllProfileItems(t *testing.T) { + ctx, store := InitializeTestStore(t) + sessionId := "session123" + publicKey := "0X13242618721" + + ctx = context.WithValue(ctx, "SessionId", sessionId) + + mockState := state.NewState(128) + mockAccountService := new(mocks.MockAccountService) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Fatal(err) + } + + flag_firstname_set, _ := fm.GetFlag("flag_firstname_set") + flag_familyname_set, _ := fm.GetFlag("flag_familyname_set") + flag_yob_set, _ := fm.GetFlag("flag_yob_set") + flag_gender_set, _ := fm.GetFlag("flag_gender_set") + flag_location_set, _ := fm.GetFlag("flag_location_set") + flag_offerings_set, _ := fm.GetFlag("flag_offerings_set") + + profileDataKeys := []storedb.DataTyp{ + storedb.DATA_FIRST_NAME, + storedb.DATA_FAMILY_NAME, + storedb.DATA_GENDER, + storedb.DATA_YOB, + storedb.DATA_LOCATION, + storedb.DATA_OFFERINGS, + } + + profileItems := []string{"John", "Doe", "Male", "1990", "Nairobi", "Software"} + + expectedResult := resource.Result{ + FlagSet: []uint32{ + flag_firstname_set, + flag_familyname_set, + flag_yob_set, + flag_gender_set, + flag_location_set, + flag_offerings_set, + }, + } + + h := &MenuHandlers{ + userdataStore: store, + flagManager: fm, + st: mockState, + accountService: mockAccountService, + profile: &profile.Profile{ + ProfileItems: profileItems, + Max: 6, + }, + } + + err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) + require.NoError(t, err) + + // Call the function under test + res, err := h.UpdateAllProfileItems(ctx, "symbol", nil) + assert.NoError(t, err) + + // Loop through profileDataKeys to validate stored values + for i, key := range profileDataKeys { + storedValue, err := store.ReadEntry(ctx, sessionId, key) + require.NoError(t, err) + assert.Equal(t, profileItems[i], string(storedValue)) + } + + assert.Equal(t, expectedResult, res) +} -- 2.45.2 From 5e166fd680cf744dc08c4d7aa2eab8afba0a242f Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 17:33:11 +0300 Subject: [PATCH 22/26] move registration tests --- handlers/application/menuhandler_test.go | 64 ------------------- handlers/application/registration_test.go | 76 +++++++++++++++++++++++ 2 files changed, 76 insertions(+), 64 deletions(-) create mode 100644 handlers/application/registration_test.go diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index a2fce4c..b87cfc5 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -208,70 +208,6 @@ func TestInit(t *testing.T) { } } -func TestCreateAccount(t *testing.T) { - sessionId := "session123" - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - _, logdb := InitializeTestLogdbStore(t) - - logDb := store.LogDb{ - Db: logdb, - } - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - - flag_account_created, err := fm.GetFlag("flag_account_created") - flag_account_creation_failed, _ := fm.GetFlag("flag_account_creation_failed") - - if err != nil { - t.Logf(err.Error()) - } - - tests := []struct { - name string - serverResponse *models.AccountResult - expectedResult resource.Result - }{ - { - name: "Test account creation success", - serverResponse: &models.AccountResult{ - TrackingId: "1234567890", - PublicKey: "0xD3adB33f", - }, - expectedResult: resource.Result{ - FlagSet: []uint32{flag_account_created}, - FlagReset: []uint32{flag_account_creation_failed}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockAccountService := new(mocks.MockAccountService) - - h := &MenuHandlers{ - userdataStore: userStore, - accountService: mockAccountService, - logDb: logDb, - flagManager: fm, - } - - mockAccountService.On("CreateAccount").Return(tt.serverResponse, nil) - - // Call the method you want to test - res, err := h.CreateAccount(ctx, "create_account", []byte("")) - - // Assert that 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") - }) - } -} - func TestWithPersister(t *testing.T) { // Test case: Setting a persister h := &MenuHandlers{} diff --git a/handlers/application/registration_test.go b/handlers/application/registration_test.go new file mode 100644 index 0000000..45e30f5 --- /dev/null +++ b/handlers/application/registration_test.go @@ -0,0 +1,76 @@ +package application + +import ( + "context" + "testing" + + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/sarafu-api/models" + "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + "github.com/alecthomas/assert/v2" +) + +func TestCreateAccount(t *testing.T) { + sessionId := "session123" + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + + flag_account_created, err := fm.GetFlag("flag_account_created") + flag_account_creation_failed, _ := fm.GetFlag("flag_account_creation_failed") + + if err != nil { + t.Logf(err.Error()) + } + + tests := []struct { + name string + serverResponse *models.AccountResult + expectedResult resource.Result + }{ + { + name: "Test account creation success", + serverResponse: &models.AccountResult{ + TrackingId: "1234567890", + PublicKey: "0xD3adB33f", + }, + expectedResult: resource.Result{ + FlagSet: []uint32{flag_account_created}, + FlagReset: []uint32{flag_account_creation_failed}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockAccountService := new(mocks.MockAccountService) + + h := &MenuHandlers{ + userdataStore: userStore, + accountService: mockAccountService, + logDb: logDb, + flagManager: fm, + } + + mockAccountService.On("CreateAccount").Return(tt.serverResponse, nil) + + // Call the method you want to test + res, err := h.CreateAccount(ctx, "create_account", []byte("")) + + // Assert that 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") + }) + } +} -- 2.45.2 From 92af428af46fb34866da6fb8fe0c5210da970122 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 17:44:55 +0300 Subject: [PATCH 23/26] move send node related tests --- handlers/application/menuhandler_test.go | 535 ---------------------- handlers/application/send_test.go | 550 +++++++++++++++++++++++ 2 files changed, 550 insertions(+), 535 deletions(-) create mode 100644 handlers/application/send_test.go diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index b87cfc5..fbd504a 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -277,77 +277,6 @@ func TestCheckIdentifier(t *testing.T) { } } -func TestGetSender(t *testing.T) { - sessionId := "session123" - ctx, _ := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - // Create the MenuHandlers instance - h := &MenuHandlers{} - - // Call the method - res, _ := h.GetSender(ctx, "get_sender", []byte("")) - - //Assert that the sessionId is what was set as the result content. - assert.Equal(t, sessionId, res.Content) -} - -func TestGetAmount(t *testing.T) { - sessionId := "session123" - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - // Define test data - amount := "0.03" - activeSym := "SRF" - - err := store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(amount)) - if err != nil { - t.Fatal(err) - } - - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(activeSym)) - if err != nil { - t.Fatal(err) - } - - // Create the MenuHandlers instance with the mock store - h := &MenuHandlers{ - userdataStore: store, - } - - // Call the method - 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, formattedAmount, res.Content) -} - -func TestGetRecipient(t *testing.T) { - sessionId := "session123" - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - recepient := "0712345678" - - err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(recepient)) - if err != nil { - t.Fatal(err) - } - - // Create the MenuHandlers instance with the mock store - h := &MenuHandlers{ - userdataStore: store, - } - - // Call the method - res, _ := h.GetRecipient(ctx, "get_recipient", []byte("")) - - //Assert that the retrieved recepient is what was set as the content - assert.Equal(t, recepient, res.Content) -} func TestGetFlag(t *testing.T) { fm, err := NewFlagManager(flagsPath) @@ -520,196 +449,6 @@ func TestVerifyCreatePin(t *testing.T) { } } -func TestTransactionReset(t *testing.T) { - sessionId := "session123" - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - - flag_invalid_recipient, _ := fm.GetFlag("flag_invalid_recipient") - flag_invalid_recipient_with_invite, _ := fm.GetFlag("flag_invalid_recipient_with_invite") - - mockAccountService := new(mocks.MockAccountService) - - h := &MenuHandlers{ - userdataStore: store, - accountService: mockAccountService, - flagManager: fm, - } - tests := []struct { - name string - input []byte - status string - expectedResult resource.Result - }{ - { - name: "Test transaction reset for amount and recipient", - expectedResult: resource.Result{ - FlagReset: []uint32{flag_invalid_recipient, flag_invalid_recipient_with_invite}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Call the method under test - res, _ := h.TransactionReset(ctx, "transaction_reset", tt.input) - - // Assert that 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") - }) - } -} - -func TestResetTransactionAmount(t *testing.T) { - sessionId := "session123" - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - - flag_invalid_amount, _ := fm.GetFlag("flag_invalid_amount") - - mockAccountService := new(mocks.MockAccountService) - - h := &MenuHandlers{ - userdataStore: store, - accountService: mockAccountService, - flagManager: fm, - } - - tests := []struct { - name string - expectedResult resource.Result - }{ - { - name: "Test amount reset", - expectedResult: resource.Result{ - FlagReset: []uint32{flag_invalid_amount}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Call the method under test - res, _ := h.ResetTransactionAmount(ctx, "transaction_reset_amount", []byte("")) - - // Assert that 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") - }) - } -} - -func TestInitiateTransaction(t *testing.T) { - sessionId := "254712345678" - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - account_authorized_flag, _ := fm.GetFlag("flag_account_authorized") - - mockAccountService := new(mocks.MockAccountService) - - h := &MenuHandlers{ - userdataStore: store, - accountService: mockAccountService, - flagManager: fm, - } - - tests := []struct { - name string - TemporaryValue []byte - ActiveSym []byte - StoredAmount []byte - TransferAmount string - PublicKey []byte - Recipient []byte - ActiveDecimal []byte - ActiveAddress []byte - TransferResponse *models.TokenTransferResponse - expectedResult resource.Result - }{ - { - name: "Test initiate transaction", - TemporaryValue: []byte("0711223344"), - ActiveSym: []byte("SRF"), - StoredAmount: []byte("1.00"), - TransferAmount: "1000000", - PublicKey: []byte("0X13242618721"), - Recipient: []byte("0x12415ass27192"), - ActiveDecimal: []byte("6"), - ActiveAddress: []byte("0xd4c288865Ce"), - TransferResponse: &models.TokenTransferResponse{ - TrackingId: "1234567890", - }, - expectedResult: resource.Result{ - FlagReset: []uint32{account_authorized_flag}, - Content: "Your request has been sent. 0711223344 will receive 1.00 SRF from 254712345678.", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.TemporaryValue)) - if err != nil { - t.Fatal(err) - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.ActiveSym)) - if err != nil { - t.Fatal(err) - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(tt.StoredAmount)) - if err != nil { - t.Fatal(err) - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(tt.PublicKey)) - if err != nil { - t.Fatal(err) - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(tt.Recipient)) - if err != nil { - t.Fatal(err) - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_DECIMAL, []byte(tt.ActiveDecimal)) - if err != nil { - t.Fatal(err) - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte(tt.ActiveAddress)) - if err != nil { - t.Fatal(err) - } - - mockAccountService.On("TokenTransfer").Return(tt.TransferResponse, nil) - - // Call the method under test - res, _ := h.InitiateTransaction(ctx, "transaction_reset_amount", []byte("")) - - // Assert that 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") - }) - } -} - func TestQuit(t *testing.T) { fm, err := NewFlagManager(flagsPath) if err != nil { @@ -757,214 +496,6 @@ func TestQuit(t *testing.T) { } } -func TestValidateAmount(t *testing.T) { - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - - sessionId := "session123" - - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - flag_invalid_amount, _ := fm.GetFlag("flag_invalid_amount") - - mockAccountService := new(mocks.MockAccountService) - - h := &MenuHandlers{ - userdataStore: store, - accountService: mockAccountService, - flagManager: fm, - } - tests := []struct { - name string - input []byte - activeBal []byte - balance string - expectedResult resource.Result - }{ - { - name: "Test with valid amount", - input: []byte("4.10"), - activeBal: []byte("5"), - expectedResult: resource.Result{ - Content: "4.10", - }, - }, - { - name: "Test with amount larger than active balance", - input: []byte("5.02"), - activeBal: []byte("5"), - expectedResult: resource.Result{ - FlagSet: []uint32{flag_invalid_amount}, - Content: "5.02", - }, - }, - { - name: "Test with invalid amount format", - input: []byte("0.02ms"), - activeBal: []byte("5"), - expectedResult: resource.Result{ - FlagSet: []uint32{flag_invalid_amount}, - Content: "0.02ms", - }, - }, - { - name: "Test with valid decimal amount", - input: []byte("0.149"), - activeBal: []byte("5"), - expectedResult: resource.Result{ - Content: "0.14", - }, - }, - { - name: "Test with valid large decimal amount", - input: []byte("1.8599999999"), - activeBal: []byte("5"), - expectedResult: resource.Result{ - Content: "1.85", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal)) - if err != nil { - t.Fatal(err) - } - - // Call the method under test - res, _ := h.ValidateAmount(ctx, "test_validate_amount", tt.input) - - // Assert no errors occurred - assert.NoError(t, err) - - // Assert the result matches the expected result - assert.Equal(t, tt.expectedResult, res, "Expected result should match actual result") - }) - } -} - -func TestValidateRecipient(t *testing.T) { - fm, err := NewFlagManager(flagsPath) - if err != nil { - log.Fatal(err) - } - - sessionId := "session123" - publicKey := "0X13242618721" - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - flag_invalid_recipient, _ := fm.GetFlag("flag_invalid_recipient") - flag_invalid_recipient_with_invite, _ := fm.GetFlag("flag_invalid_recipient_with_invite") - - // Define test cases - tests := []struct { - name string - input []byte - expectError bool - expectedRecipient []byte - expectedResult resource.Result - }{ - { - name: "Test with invalid recepient", - input: []byte("7?1234"), - expectError: true, - expectedResult: resource.Result{ - FlagSet: []uint32{flag_invalid_recipient}, - Content: "7?1234", - }, - }, - { - name: "Test with valid unregistered recepient", - input: []byte("0712345678"), - expectError: true, - expectedResult: resource.Result{ - FlagSet: []uint32{flag_invalid_recipient_with_invite}, - Content: "0712345678", - }, - }, - { - name: "Test with valid registered recepient", - input: []byte("0711223344"), - expectError: false, - expectedRecipient: []byte(publicKey), - expectedResult: resource.Result{}, - }, - { - name: "Test with address", - input: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"), - expectError: false, - expectedRecipient: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"), - expectedResult: resource.Result{}, - }, - { - name: "Test with alias recepient", - input: []byte("alias123.sarafu.local"), - expectError: false, - expectedRecipient: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"), - expectedResult: resource.Result{}, - }, - { - name: "Test for checksummed address", - input: []byte("0x5523058cdffe5f3c1eadadd5015e55c6e00fb439"), - expectError: false, - expectedRecipient: []byte("0x5523058cdFfe5F3c1EaDADD5015E55C6E00fb439"), - expectedResult: resource.Result{}, - }, - { - name: "Test with valid registered recepient that has white spaces", - input: []byte("0711 22 33 44"), - expectError: false, - expectedRecipient: []byte(publicKey), - expectedResult: resource.Result{}, - }, - } - - // store a public key for the valid recipient - err = store.WriteEntry(ctx, "+254711223344", storedb.DATA_PUBLIC_KEY, []byte(publicKey)) - if err != nil { - t.Fatal(err) - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockAccountService := new(mocks.MockAccountService) - // Create the MenuHandlers instance - h := &MenuHandlers{ - flagManager: fm, - userdataStore: store, - accountService: mockAccountService, - } - - aliasResponse := &models.AliasAddress{ - Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", - } - - mockAccountService.On("CheckAliasAddress", string(tt.input)).Return(aliasResponse, nil) - - // Call the method - res, err := h.ValidateRecipient(ctx, "validate_recepient", tt.input) - - if err != nil { - t.Error(err) - } - - if !tt.expectError { - storedRecipientAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_RECIPIENT) - assert.NoError(t, err) - assert.Equal(t, tt.expectedRecipient, storedRecipientAddress) - } - - // Assert that the Result FlagSet has the required flags after language switch - assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset") - }) - } -} - func TestManageVouchers(t *testing.T) { sessionId := "session123" publicKey := "0X13242618721" @@ -1505,72 +1036,6 @@ func TestRetrieveBlockedNumber(t *testing.T) { assert.Equal(t, blockedNumber, res.Content) } -func TestMaxAmount(t *testing.T) { - sessionId := "session123" - activeBal := "500" - - tests := []struct { - name string - sessionId string - activeBal string - expectedError bool - expectedResult resource.Result - }{ - { - name: "Valid session ID and active balance", - sessionId: sessionId, - activeBal: activeBal, - expectedError: false, - expectedResult: resource.Result{Content: activeBal}, - }, - { - name: "Missing Session ID", - sessionId: "", - activeBal: activeBal, - expectedError: true, - expectedResult: resource.Result{}, - }, - { - name: "Failed to Read Active Balance", - sessionId: sessionId, - activeBal: "", // failure to read active balance - expectedError: true, - expectedResult: resource.Result{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx, userStore := InitializeTestStore(t) - if tt.sessionId != "" { - ctx = context.WithValue(ctx, "SessionId", tt.sessionId) - } - - h := &MenuHandlers{ - userdataStore: userStore, - } - - // Write active balance to the store only if it's not empty - if tt.activeBal != "" { - err := userStore.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal)) - if err != nil { - t.Fatal(err) - } - } - - res, err := h.MaxAmount(ctx, "max_amount", []byte("")) - - if tt.expectedError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - - assert.Equal(t, tt.expectedResult, res) - }) - } -} - func TestQuitWithHelp(t *testing.T) { fm, err := NewFlagManager(flagsPath) if err != nil { diff --git a/handlers/application/send_test.go b/handlers/application/send_test.go new file mode 100644 index 0000000..cde3598 --- /dev/null +++ b/handlers/application/send_test.go @@ -0,0 +1,550 @@ +package application + +import ( + "context" + "fmt" + "log" + "testing" + + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/sarafu-api/models" + "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/alecthomas/assert/v2" +) + +func TestValidateRecipient(t *testing.T) { + fm, err := NewFlagManager(flagsPath) + if err != nil { + log.Fatal(err) + } + + sessionId := "session123" + publicKey := "0X13242618721" + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + flag_invalid_recipient, _ := fm.GetFlag("flag_invalid_recipient") + flag_invalid_recipient_with_invite, _ := fm.GetFlag("flag_invalid_recipient_with_invite") + + // Define test cases + tests := []struct { + name string + input []byte + expectError bool + expectedRecipient []byte + expectedResult resource.Result + }{ + { + name: "Test with invalid recepient", + input: []byte("7?1234"), + expectError: true, + expectedResult: resource.Result{ + FlagSet: []uint32{flag_invalid_recipient}, + Content: "7?1234", + }, + }, + { + name: "Test with valid unregistered recepient", + input: []byte("0712345678"), + expectError: true, + expectedResult: resource.Result{ + FlagSet: []uint32{flag_invalid_recipient_with_invite}, + Content: "0712345678", + }, + }, + { + name: "Test with valid registered recepient", + input: []byte("0711223344"), + expectError: false, + expectedRecipient: []byte(publicKey), + expectedResult: resource.Result{}, + }, + { + name: "Test with address", + input: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"), + expectError: false, + expectedRecipient: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"), + expectedResult: resource.Result{}, + }, + { + name: "Test with alias recepient", + input: []byte("alias123.sarafu.local"), + expectError: false, + expectedRecipient: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"), + expectedResult: resource.Result{}, + }, + { + name: "Test for checksummed address", + input: []byte("0x5523058cdffe5f3c1eadadd5015e55c6e00fb439"), + expectError: false, + expectedRecipient: []byte("0x5523058cdFfe5F3c1EaDADD5015E55C6E00fb439"), + expectedResult: resource.Result{}, + }, + { + name: "Test with valid registered recepient that has white spaces", + input: []byte("0711 22 33 44"), + expectError: false, + expectedRecipient: []byte(publicKey), + expectedResult: resource.Result{}, + }, + } + + // store a public key for the valid recipient + err = store.WriteEntry(ctx, "+254711223344", storedb.DATA_PUBLIC_KEY, []byte(publicKey)) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockAccountService := new(mocks.MockAccountService) + // Create the MenuHandlers instance + h := &MenuHandlers{ + flagManager: fm, + userdataStore: store, + accountService: mockAccountService, + } + + aliasResponse := &models.AliasAddress{ + Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + } + + mockAccountService.On("CheckAliasAddress", string(tt.input)).Return(aliasResponse, nil) + + // Call the method + res, err := h.ValidateRecipient(ctx, "validate_recepient", tt.input) + + if err != nil { + t.Error(err) + } + + if !tt.expectError { + storedRecipientAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_RECIPIENT) + assert.NoError(t, err) + assert.Equal(t, tt.expectedRecipient, storedRecipientAddress) + } + + // Assert that the Result FlagSet has the required flags after language switch + assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset") + }) + } +} + +func TestTransactionReset(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + + flag_invalid_recipient, _ := fm.GetFlag("flag_invalid_recipient") + flag_invalid_recipient_with_invite, _ := fm.GetFlag("flag_invalid_recipient_with_invite") + + mockAccountService := new(mocks.MockAccountService) + + h := &MenuHandlers{ + userdataStore: store, + accountService: mockAccountService, + flagManager: fm, + } + tests := []struct { + name string + input []byte + status string + expectedResult resource.Result + }{ + { + name: "Test transaction reset for amount and recipient", + expectedResult: resource.Result{ + FlagReset: []uint32{flag_invalid_recipient, flag_invalid_recipient_with_invite}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Call the method under test + res, _ := h.TransactionReset(ctx, "transaction_reset", tt.input) + + // Assert that 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") + }) + } +} + +func TestResetTransactionAmount(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + + flag_invalid_amount, _ := fm.GetFlag("flag_invalid_amount") + + mockAccountService := new(mocks.MockAccountService) + + h := &MenuHandlers{ + userdataStore: store, + accountService: mockAccountService, + flagManager: fm, + } + + tests := []struct { + name string + expectedResult resource.Result + }{ + { + name: "Test amount reset", + expectedResult: resource.Result{ + FlagReset: []uint32{flag_invalid_amount}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Call the method under test + res, _ := h.ResetTransactionAmount(ctx, "transaction_reset_amount", []byte("")) + + // Assert that 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") + }) + } +} + +func TestMaxAmount(t *testing.T) { + sessionId := "session123" + activeBal := "500" + + tests := []struct { + name string + sessionId string + activeBal string + expectedError bool + expectedResult resource.Result + }{ + { + name: "Valid session ID and active balance", + sessionId: sessionId, + activeBal: activeBal, + expectedError: false, + expectedResult: resource.Result{Content: activeBal}, + }, + { + name: "Missing Session ID", + sessionId: "", + activeBal: activeBal, + expectedError: true, + expectedResult: resource.Result{}, + }, + { + name: "Failed to Read Active Balance", + sessionId: sessionId, + activeBal: "", // failure to read active balance + expectedError: true, + expectedResult: resource.Result{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, userStore := InitializeTestStore(t) + if tt.sessionId != "" { + ctx = context.WithValue(ctx, "SessionId", tt.sessionId) + } + + h := &MenuHandlers{ + userdataStore: userStore, + } + + // Write active balance to the store only if it's not empty + if tt.activeBal != "" { + err := userStore.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal)) + if err != nil { + t.Fatal(err) + } + } + + res, err := h.MaxAmount(ctx, "max_amount", []byte("")) + + if tt.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tt.expectedResult, res) + }) + } +} + +func TestValidateAmount(t *testing.T) { + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + + sessionId := "session123" + + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + flag_invalid_amount, _ := fm.GetFlag("flag_invalid_amount") + + mockAccountService := new(mocks.MockAccountService) + + h := &MenuHandlers{ + userdataStore: store, + accountService: mockAccountService, + flagManager: fm, + } + tests := []struct { + name string + input []byte + activeBal []byte + balance string + expectedResult resource.Result + }{ + { + name: "Test with valid amount", + input: []byte("4.10"), + activeBal: []byte("5"), + expectedResult: resource.Result{ + Content: "4.10", + }, + }, + { + name: "Test with amount larger than active balance", + input: []byte("5.02"), + activeBal: []byte("5"), + expectedResult: resource.Result{ + FlagSet: []uint32{flag_invalid_amount}, + Content: "5.02", + }, + }, + { + name: "Test with invalid amount format", + input: []byte("0.02ms"), + activeBal: []byte("5"), + expectedResult: resource.Result{ + FlagSet: []uint32{flag_invalid_amount}, + Content: "0.02ms", + }, + }, + { + name: "Test with valid decimal amount", + input: []byte("0.149"), + activeBal: []byte("5"), + expectedResult: resource.Result{ + Content: "0.14", + }, + }, + { + name: "Test with valid large decimal amount", + input: []byte("1.8599999999"), + activeBal: []byte("5"), + expectedResult: resource.Result{ + Content: "1.85", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal)) + if err != nil { + t.Fatal(err) + } + + // Call the method under test + res, _ := h.ValidateAmount(ctx, "test_validate_amount", tt.input) + + // Assert no errors occurred + assert.NoError(t, err) + + // Assert the result matches the expected result + assert.Equal(t, tt.expectedResult, res, "Expected result should match actual result") + }) + } +} + +func TestGetRecipient(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + recepient := "0712345678" + + err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(recepient)) + if err != nil { + t.Fatal(err) + } + + // Create the MenuHandlers instance with the mock store + h := &MenuHandlers{ + userdataStore: store, + } + + // Call the method + res, _ := h.GetRecipient(ctx, "get_recipient", []byte("")) + + //Assert that the retrieved recepient is what was set as the content + assert.Equal(t, recepient, res.Content) +} + +func TestGetSender(t *testing.T) { + sessionId := "session123" + ctx, _ := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + // Create the MenuHandlers instance + h := &MenuHandlers{} + + // Call the method + res, _ := h.GetSender(ctx, "get_sender", []byte("")) + + //Assert that the sessionId is what was set as the result content. + assert.Equal(t, sessionId, res.Content) +} + +func TestGetAmount(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + // Define test data + amount := "0.03" + activeSym := "SRF" + + err := store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(amount)) + if err != nil { + t.Fatal(err) + } + + err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(activeSym)) + if err != nil { + t.Fatal(err) + } + + // Create the MenuHandlers instance with the mock store + h := &MenuHandlers{ + userdataStore: store, + } + + // Call the method + 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, formattedAmount, res.Content) +} + +func TestInitiateTransaction(t *testing.T) { + sessionId := "254712345678" + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + account_authorized_flag, _ := fm.GetFlag("flag_account_authorized") + + mockAccountService := new(mocks.MockAccountService) + + h := &MenuHandlers{ + userdataStore: store, + accountService: mockAccountService, + flagManager: fm, + } + + tests := []struct { + name string + TemporaryValue []byte + ActiveSym []byte + StoredAmount []byte + TransferAmount string + PublicKey []byte + Recipient []byte + ActiveDecimal []byte + ActiveAddress []byte + TransferResponse *models.TokenTransferResponse + expectedResult resource.Result + }{ + { + name: "Test initiate transaction", + TemporaryValue: []byte("0711223344"), + ActiveSym: []byte("SRF"), + StoredAmount: []byte("1.00"), + TransferAmount: "1000000", + PublicKey: []byte("0X13242618721"), + Recipient: []byte("0x12415ass27192"), + ActiveDecimal: []byte("6"), + ActiveAddress: []byte("0xd4c288865Ce"), + TransferResponse: &models.TokenTransferResponse{ + TrackingId: "1234567890", + }, + expectedResult: resource.Result{ + FlagReset: []uint32{account_authorized_flag}, + Content: "Your request has been sent. 0711223344 will receive 1.00 SRF from 254712345678.", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.TemporaryValue)) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.ActiveSym)) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(tt.StoredAmount)) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(tt.PublicKey)) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(tt.Recipient)) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_DECIMAL, []byte(tt.ActiveDecimal)) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte(tt.ActiveAddress)) + if err != nil { + t.Fatal(err) + } + + mockAccountService.On("TokenTransfer").Return(tt.TransferResponse, nil) + + // Call the method under test + res, _ := h.InitiateTransaction(ctx, "transaction_reset_amount", []byte("")) + + // Assert that 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") + }) + } +} -- 2.45.2 From a55d254c4f6c37cd452a55c10e151a3c46174981 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 17:52:30 +0300 Subject: [PATCH 24/26] move transaction list related tests --- handlers/application/menuhandler_test.go | 252 -------------------- handlers/application/transactions_test.go | 265 ++++++++++++++++++++++ 2 files changed, 265 insertions(+), 252 deletions(-) create mode 100644 handlers/application/transactions_test.go diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index fbd504a..83f28de 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -8,7 +8,6 @@ import ( "strconv" "strings" "testing" - "time" "git.defalsify.org/vise.git/cache" "git.defalsify.org/vise.git/persist" @@ -277,7 +276,6 @@ func TestCheckIdentifier(t *testing.T) { } } - func TestGetFlag(t *testing.T) { fm, err := NewFlagManager(flagsPath) expectedFlag := uint32(9) @@ -763,256 +761,6 @@ func TestGetVoucherDetails(t *testing.T) { assert.Equal(t, expectedResult, res) } -func TestCheckTransactions(t *testing.T) { - mockAccountService := new(mocks.MockAccountService) - sessionId := "session123" - publicKey := "0X13242618721" - - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - _, logdb := InitializeTestLogdbStore(t) - - logDb := store.LogDb{ - Db: logdb, - } - - spdb := InitializeTestSubPrefixDb(t, ctx) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - - h := &MenuHandlers{ - userdataStore: userStore, - accountService: mockAccountService, - prefixDb: spdb, - logDb: logDb, - flagManager: fm, - } - - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) - if err != nil { - t.Fatal(err) - } - - mockTXResponse := []dataserviceapi.Last10TxResponse{ - { - Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "100", ContractAddress: "0X1324262343rfdGW23", - TxHash: "0x123wefsf34rf", DateBlock: time.Now(), TokenSymbol: "SRF", TokenDecimals: "6", - }, - { - Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "200", ContractAddress: "0X1324262343rfdGW23", - TxHash: "0xq34wresfdb44", DateBlock: time.Now(), TokenSymbol: "SRF", TokenDecimals: "6", - }, - } - - expectedSenders := []byte("0X13242618721\n0x41c188d63Qa") - - mockAccountService.On("FetchTransactions", string(publicKey)).Return(mockTXResponse, nil) - - _, err = h.CheckTransactions(ctx, "check_transactions", []byte("")) - assert.NoError(t, err) - - // Read tranfers senders data from the store - senderData, err := spdb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SENDERS)) - if err != nil { - t.Fatal(err) - } - - // assert that the data is stored correctly - assert.Equal(t, expectedSenders, senderData) - - mockAccountService.AssertExpectations(t) -} - -func TestGetTransactionsList(t *testing.T) { - sessionId := "session123" - publicKey := "0X13242618721" - - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - spdb := InitializeTestSubPrefixDb(t, ctx) - - // Initialize MenuHandlers - h := &MenuHandlers{ - userdataStore: userStore, - prefixDb: spdb, - ReplaceSeparatorFunc: mockReplaceSeparator, - } - - err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) - if err != nil { - t.Fatal(err) - } - - dateBlock, err := time.Parse(time.RFC3339, "2024-10-03T07:23:12Z") - if err != nil { - t.Fatal(err) - } - - mockTXResponse := []dataserviceapi.Last10TxResponse{ - { - Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "1000", ContractAddress: "0X1324262343rfdGW23", - TxHash: "0x123wefsf34rf", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2", - }, - { - Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "2000", ContractAddress: "0X1324262343rfdGW23", - TxHash: "0xq34wresfdb44", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2", - }, - } - - data := store.ProcessTransfers(mockTXResponse) - - // Store all transaction data - dataMap := map[storedb.DataTyp]string{ - storedb.DATA_TX_SENDERS: data.Senders, - storedb.DATA_TX_RECIPIENTS: data.Recipients, - storedb.DATA_TX_VALUES: data.TransferValues, - storedb.DATA_TX_ADDRESSES: data.Addresses, - storedb.DATA_TX_HASHES: data.TxHashes, - storedb.DATA_TX_DATES: data.Dates, - storedb.DATA_TX_SYMBOLS: data.Symbols, - storedb.DATA_TX_DECIMALS: data.Decimals, - } - - for key, value := range dataMap { - if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil { - t.Fatal(err) - } - } - - expectedTransactionList := []byte("1: Sent 10 SRF 2024-10-03\n2: Received 20 SRF 2024-10-03") - - res, err := h.GetTransactionsList(ctx, "", []byte("")) - - assert.NoError(t, err) - assert.Equal(t, res.Content, string(expectedTransactionList)) -} - -func TestViewTransactionStatement(t *testing.T) { - ctx, userStore := InitializeTestStore(t) - sessionId := "session123" - publicKey := "0X13242618721" - - ctx = context.WithValue(ctx, "SessionId", sessionId) - spdb := InitializeTestSubPrefixDb(t, ctx) - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - flag_incorrect_statement, _ := fm.GetFlag("flag_incorrect_statement") - - h := &MenuHandlers{ - userdataStore: userStore, - prefixDb: spdb, - flagManager: fm, - } - - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) - if err != nil { - t.Fatal(err) - } - - dateBlock, err := time.Parse(time.RFC3339, "2024-10-03T07:23:12Z") - if err != nil { - t.Fatal(err) - } - - mockTXResponse := []dataserviceapi.Last10TxResponse{ - { - Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "1000", ContractAddress: "0X1324262343rfdGW23", - TxHash: "0x123wefsf34rf", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2", - }, - { - Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "2000", ContractAddress: "0X1324262343rfdGW23", - TxHash: "0xq34wresfdb44", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2", - }, - } - - data := store.ProcessTransfers(mockTXResponse) - - // Store all transaction data - dataMap := map[storedb.DataTyp]string{ - storedb.DATA_TX_SENDERS: data.Senders, - storedb.DATA_TX_RECIPIENTS: data.Recipients, - storedb.DATA_TX_VALUES: data.TransferValues, - storedb.DATA_TX_ADDRESSES: data.Addresses, - storedb.DATA_TX_HASHES: data.TxHashes, - storedb.DATA_TX_DATES: data.Dates, - storedb.DATA_TX_SYMBOLS: data.Symbols, - storedb.DATA_TX_DECIMALS: data.Decimals, - } - - for key, value := range dataMap { - if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil { - t.Fatal(err) - } - } - - tests := []struct { - name string - input []byte - expectedError error - expectedResult resource.Result - }{ - { - name: "Valid input - index 1", - input: []byte("1"), - expectedError: nil, - expectedResult: resource.Result{ - Content: "Sent 10 SRF\nTo: 0x41c188d63Qa\nContract address: 0X1324262343rfdGW23\nTxhash: 0x123wefsf34rf\nDate: 2024-10-03 07:23:12 AM", - FlagReset: []uint32{flag_incorrect_statement}, - }, - }, - { - name: "Valid input - index 2", - input: []byte("2"), - expectedError: nil, - expectedResult: resource.Result{ - Content: "Received 20 SRF\nFrom: 0x41c188d63Qa\nContract address: 0X1324262343rfdGW23\nTxhash: 0xq34wresfdb44\nDate: 2024-10-03 07:23:12 AM", - FlagReset: []uint32{flag_incorrect_statement}, - }, - }, - { - name: "Invalid input - index 0", - input: []byte("0"), - expectedError: nil, - expectedResult: resource.Result{ - FlagReset: []uint32{flag_incorrect_statement}, - }, - }, - { - name: "Invalid input - index 12", - input: []byte("12"), - expectedError: fmt.Errorf("invalid input: index must be between 1 and 10"), - expectedResult: resource.Result{}, - }, - { - name: "Invalid input - non-numeric", - input: []byte("abc"), - expectedError: fmt.Errorf("invalid input: must be a number between 1 and 10"), - expectedResult: resource.Result{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - res, err := h.ViewTransactionStatement(ctx, "view_transaction_statement", tt.input) - - if tt.expectedError != nil { - assert.EqualError(t, err, tt.expectedError.Error()) - } else { - assert.NoError(t, err) - } - - assert.Equal(t, tt.expectedResult, res) - }) - } -} - func TestRetrieveBlockedNumber(t *testing.T) { sessionId := "session123" blockedNumber := "0712345678" diff --git a/handlers/application/transactions_test.go b/handlers/application/transactions_test.go new file mode 100644 index 0000000..b842c31 --- /dev/null +++ b/handlers/application/transactions_test.go @@ -0,0 +1,265 @@ +package application + +import ( + "context" + "fmt" + "testing" + "time" + + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/alecthomas/assert/v2" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" +) + +func TestCheckTransactions(t *testing.T) { + mockAccountService := new(mocks.MockAccountService) + sessionId := "session123" + publicKey := "0X13242618721" + + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } + + spdb := InitializeTestSubPrefixDb(t, ctx) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + + h := &MenuHandlers{ + userdataStore: userStore, + accountService: mockAccountService, + prefixDb: spdb, + logDb: logDb, + flagManager: fm, + } + + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) + if err != nil { + t.Fatal(err) + } + + mockTXResponse := []dataserviceapi.Last10TxResponse{ + { + Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "100", ContractAddress: "0X1324262343rfdGW23", + TxHash: "0x123wefsf34rf", DateBlock: time.Now(), TokenSymbol: "SRF", TokenDecimals: "6", + }, + { + Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "200", ContractAddress: "0X1324262343rfdGW23", + TxHash: "0xq34wresfdb44", DateBlock: time.Now(), TokenSymbol: "SRF", TokenDecimals: "6", + }, + } + + expectedSenders := []byte("0X13242618721\n0x41c188d63Qa") + + mockAccountService.On("FetchTransactions", string(publicKey)).Return(mockTXResponse, nil) + + _, err = h.CheckTransactions(ctx, "check_transactions", []byte("")) + assert.NoError(t, err) + + // Read tranfers senders data from the store + senderData, err := spdb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SENDERS)) + if err != nil { + t.Fatal(err) + } + + // assert that the data is stored correctly + assert.Equal(t, expectedSenders, senderData) + + mockAccountService.AssertExpectations(t) +} + +func TestGetTransactionsList(t *testing.T) { + sessionId := "session123" + publicKey := "0X13242618721" + + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + spdb := InitializeTestSubPrefixDb(t, ctx) + + // Initialize MenuHandlers + h := &MenuHandlers{ + userdataStore: userStore, + prefixDb: spdb, + ReplaceSeparatorFunc: mockReplaceSeparator, + } + + err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) + if err != nil { + t.Fatal(err) + } + + dateBlock, err := time.Parse(time.RFC3339, "2024-10-03T07:23:12Z") + if err != nil { + t.Fatal(err) + } + + mockTXResponse := []dataserviceapi.Last10TxResponse{ + { + Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "1000", ContractAddress: "0X1324262343rfdGW23", + TxHash: "0x123wefsf34rf", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2", + }, + { + Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "2000", ContractAddress: "0X1324262343rfdGW23", + TxHash: "0xq34wresfdb44", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2", + }, + } + + data := store.ProcessTransfers(mockTXResponse) + + // Store all transaction data + dataMap := map[storedb.DataTyp]string{ + storedb.DATA_TX_SENDERS: data.Senders, + storedb.DATA_TX_RECIPIENTS: data.Recipients, + storedb.DATA_TX_VALUES: data.TransferValues, + storedb.DATA_TX_ADDRESSES: data.Addresses, + storedb.DATA_TX_HASHES: data.TxHashes, + storedb.DATA_TX_DATES: data.Dates, + storedb.DATA_TX_SYMBOLS: data.Symbols, + storedb.DATA_TX_DECIMALS: data.Decimals, + } + + for key, value := range dataMap { + if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil { + t.Fatal(err) + } + } + + expectedTransactionList := []byte("1: Sent 10 SRF 2024-10-03\n2: Received 20 SRF 2024-10-03") + + res, err := h.GetTransactionsList(ctx, "", []byte("")) + + assert.NoError(t, err) + assert.Equal(t, res.Content, string(expectedTransactionList)) +} + +func TestViewTransactionStatement(t *testing.T) { + ctx, userStore := InitializeTestStore(t) + sessionId := "session123" + publicKey := "0X13242618721" + + ctx = context.WithValue(ctx, "SessionId", sessionId) + spdb := InitializeTestSubPrefixDb(t, ctx) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + flag_incorrect_statement, _ := fm.GetFlag("flag_incorrect_statement") + + h := &MenuHandlers{ + userdataStore: userStore, + prefixDb: spdb, + flagManager: fm, + } + + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) + if err != nil { + t.Fatal(err) + } + + dateBlock, err := time.Parse(time.RFC3339, "2024-10-03T07:23:12Z") + if err != nil { + t.Fatal(err) + } + + mockTXResponse := []dataserviceapi.Last10TxResponse{ + { + Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "1000", ContractAddress: "0X1324262343rfdGW23", + TxHash: "0x123wefsf34rf", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2", + }, + { + Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "2000", ContractAddress: "0X1324262343rfdGW23", + TxHash: "0xq34wresfdb44", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2", + }, + } + + data := store.ProcessTransfers(mockTXResponse) + + // Store all transaction data + dataMap := map[storedb.DataTyp]string{ + storedb.DATA_TX_SENDERS: data.Senders, + storedb.DATA_TX_RECIPIENTS: data.Recipients, + storedb.DATA_TX_VALUES: data.TransferValues, + storedb.DATA_TX_ADDRESSES: data.Addresses, + storedb.DATA_TX_HASHES: data.TxHashes, + storedb.DATA_TX_DATES: data.Dates, + storedb.DATA_TX_SYMBOLS: data.Symbols, + storedb.DATA_TX_DECIMALS: data.Decimals, + } + + for key, value := range dataMap { + if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil { + t.Fatal(err) + } + } + + tests := []struct { + name string + input []byte + expectedError error + expectedResult resource.Result + }{ + { + name: "Valid input - index 1", + input: []byte("1"), + expectedError: nil, + expectedResult: resource.Result{ + Content: "Sent 10 SRF\nTo: 0x41c188d63Qa\nContract address: 0X1324262343rfdGW23\nTxhash: 0x123wefsf34rf\nDate: 2024-10-03 07:23:12 AM", + FlagReset: []uint32{flag_incorrect_statement}, + }, + }, + { + name: "Valid input - index 2", + input: []byte("2"), + expectedError: nil, + expectedResult: resource.Result{ + Content: "Received 20 SRF\nFrom: 0x41c188d63Qa\nContract address: 0X1324262343rfdGW23\nTxhash: 0xq34wresfdb44\nDate: 2024-10-03 07:23:12 AM", + FlagReset: []uint32{flag_incorrect_statement}, + }, + }, + { + name: "Invalid input - index 0", + input: []byte("0"), + expectedError: nil, + expectedResult: resource.Result{ + FlagReset: []uint32{flag_incorrect_statement}, + }, + }, + { + name: "Invalid input - index 12", + input: []byte("12"), + expectedError: fmt.Errorf("invalid input: index must be between 1 and 10"), + expectedResult: resource.Result{}, + }, + { + name: "Invalid input - non-numeric", + input: []byte("abc"), + expectedError: fmt.Errorf("invalid input: must be a number between 1 and 10"), + expectedResult: resource.Result{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := h.ViewTransactionStatement(ctx, "view_transaction_statement", tt.input) + + if tt.expectedError != nil { + assert.EqualError(t, err, tt.expectedError.Error()) + } else { + assert.NoError(t, err) + } + + assert.Equal(t, tt.expectedResult, res) + }) + } +} -- 2.45.2 From c8a705ff44d311ded6a3e7192ed48b15c325af77 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 3 Jul 2025 17:57:24 +0300 Subject: [PATCH 25/26] move voucher related tests --- handlers/application/menuhandler_test.go | 270 ---------------------- handlers/application/vouchers_test.go | 282 +++++++++++++++++++++++ 2 files changed, 282 insertions(+), 270 deletions(-) create mode 100644 handlers/application/vouchers_test.go diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index 83f28de..674c60a 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -2,7 +2,6 @@ package application import ( "context" - "fmt" "log" "path" "strconv" @@ -14,7 +13,6 @@ import ( "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/state" "git.grassecon.net/grassrootseconomics/common/pin" - "git.grassecon.net/grassrootseconomics/sarafu-api/models" "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks" "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/testservice" "git.grassecon.net/grassrootseconomics/sarafu-vise/store" @@ -27,7 +25,6 @@ import ( visedb "git.defalsify.org/vise.git/db" memdb "git.defalsify.org/vise.git/db/mem" - dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) var ( @@ -494,273 +491,6 @@ func TestQuit(t *testing.T) { } } -func TestManageVouchers(t *testing.T) { - sessionId := "session123" - publicKey := "0X13242618721" - - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - _, logdb := InitializeTestLogdbStore(t) - - logDb := store.LogDb{ - Db: logdb, - } - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Fatal(err) - } - flag_no_active_voucher, err := fm.GetFlag("flag_no_active_voucher") - if err != nil { - t.Fatal(err) - } - flag_api_error, err := fm.GetFlag("flag_api_call_error") - - if err != nil { - t.Fatal(err) - } - - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) - if err != nil { - t.Fatal(err) - } - - tests := []struct { - name string - vouchersResp []dataserviceapi.TokenHoldings - storedActiveVoucher string - expectedVoucherSymbols []byte - expectedUpdatedAddress []byte - expectedResult resource.Result - }{ - { - name: "No vouchers available", - vouchersResp: []dataserviceapi.TokenHoldings{}, - expectedVoucherSymbols: []byte(""), - expectedUpdatedAddress: []byte(""), - expectedResult: resource.Result{ - FlagSet: []uint32{flag_no_active_voucher}, - FlagReset: []uint32{flag_api_error}, - }, - }, - { - name: "Set default voucher when no active voucher is set", - vouchersResp: []dataserviceapi.TokenHoldings{ - { - TokenAddress: "0x123", - TokenSymbol: "TOKEN1", - TokenDecimals: "18", - Balance: "100", - }, - }, - expectedVoucherSymbols: []byte("1:TOKEN1"), - expectedUpdatedAddress: []byte("0x123"), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_api_error, flag_no_active_voucher}, - }, - }, - { - name: "Check and update active voucher balance", - vouchersResp: []dataserviceapi.TokenHoldings{ - {TokenAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, - {TokenAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, - }, - storedActiveVoucher: "SRF", - expectedVoucherSymbols: []byte("1:SRF\n2:MILO"), - expectedUpdatedAddress: []byte("0xd4c288865Ce"), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_api_error, flag_no_active_voucher}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockAccountService := new(mocks.MockAccountService) - - h := &MenuHandlers{ - userdataStore: userStore, - accountService: mockAccountService, - flagManager: fm, - logDb: logDb, - } - - mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil) - - // Store active voucher if needed - if tt.storedActiveVoucher != "" { - err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.storedActiveVoucher)) - if err != nil { - t.Fatal(err) - } - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte("0x41c188D45rfg6ds")) - if err != nil { - t.Fatal(err) - } - } - - res, err := h.ManageVouchers(ctx, "manage_vouchers", []byte("")) - assert.NoError(t, err) - assert.Equal(t, tt.expectedResult, res) - - if tt.storedActiveVoucher != "" { - // Validate stored voucher symbols - voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS) - assert.NoError(t, err) - assert.Equal(t, tt.expectedVoucherSymbols, voucherData) - - // Validate stored active contract address - updatedAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) - assert.NoError(t, err) - assert.Equal(t, tt.expectedUpdatedAddress, updatedAddress) - - mockAccountService.AssertExpectations(t) - } - }) - } -} - -func TestGetVoucherList(t *testing.T) { - sessionId := "session123" - - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - // Initialize MenuHandlers - h := &MenuHandlers{ - userdataStore: store, - ReplaceSeparatorFunc: mockReplaceSeparator, - } - - mockSymbols := []byte("1:SRF\n2:MILO") - mockBalances := []byte("1:10.099999\n2:40.7") - - // Put voucher symnols and balances data to the store - err := store.WriteEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS, mockSymbols) - if err != nil { - t.Fatal(err) - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_VOUCHER_BALANCES, mockBalances) - if err != nil { - t.Fatal(err) - } - - expectedList := []byte("1: SRF 10.09\n2: MILO 40.70") - - res, err := h.GetVoucherList(ctx, "", []byte("")) - - assert.NoError(t, err) - assert.Equal(t, res.Content, string(expectedList)) -} - -func TestViewVoucher(t *testing.T) { - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - ctx, store := InitializeTestStore(t) - sessionId := "session123" - ctx = context.WithValue(ctx, "SessionId", sessionId) - - h := &MenuHandlers{ - userdataStore: store, - flagManager: fm, - } - - // Define mock voucher data - mockData := map[storedb.DataTyp][]byte{ - storedb.DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"), - storedb.DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"), - storedb.DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"), - storedb.DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), - } - - // Put the data - for key, value := range mockData { - err := store.WriteEntry(ctx, sessionId, key, []byte(value)) - if err != nil { - t.Fatal(err) - } - } - - res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1")) - assert.NoError(t, err) - assert.Equal(t, res.Content, "Symbol: SRF\nBalance: 100") -} - -func TestSetVoucher(t *testing.T) { - ctx, store := InitializeTestStore(t) - sessionId := "session123" - - ctx = context.WithValue(ctx, "SessionId", sessionId) - - h := &MenuHandlers{ - userdataStore: store, - } - - // Define the temporary voucher data - tempData := &dataserviceapi.TokenHoldings{ - TokenSymbol: "SRF", - Balance: "200", - TokenDecimals: "6", - TokenAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", - } - - expectedData := fmt.Sprintf("%s,%s,%s,%s", tempData.TokenSymbol, tempData.Balance, tempData.TokenDecimals, tempData.TokenAddress) - - // store the expectedData - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(expectedData)); err != nil { - t.Fatal(err) - } - - res, err := h.SetVoucher(ctx, "set_voucher", []byte("")) - - assert.NoError(t, err) - - assert.Equal(t, string(tempData.TokenSymbol), res.Content) -} - -func TestGetVoucherDetails(t *testing.T) { - ctx, store := InitializeTestStore(t) - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Logf(err.Error()) - } - - flag_api_error, _ := fm.GetFlag("flag_api_call_error") - mockAccountService := new(mocks.MockAccountService) - - sessionId := "session123" - ctx = context.WithValue(ctx, "SessionId", sessionId) - expectedResult := resource.Result{} - - tokA_AAddress := "0x0000000000000000000000000000000000000000" - - h := &MenuHandlers{ - userdataStore: store, - flagManager: fm, - accountService: mockAccountService, - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte(tokA_AAddress)) - if err != nil { - t.Fatal(err) - } - tokenDetails := &models.VoucherDataResult{ - TokenName: "Token A", - TokenSymbol: "TOKA", - TokenLocation: "Kilifi,Kenya", - TokenCommodity: "Farming", - } - expectedResult.Content = fmt.Sprintf( - "Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", tokenDetails.TokenName, tokenDetails.TokenSymbol, tokenDetails.TokenCommodity, tokenDetails.TokenLocation, - ) - mockAccountService.On("VoucherData", string(tokA_AAddress)).Return(tokenDetails, nil) - res, err := h.GetVoucherDetails(ctx, "SessionId", []byte("")) - expectedResult.FlagReset = append(expectedResult.FlagReset, flag_api_error) - assert.NoError(t, err) - assert.Equal(t, expectedResult, res) -} - func TestRetrieveBlockedNumber(t *testing.T) { sessionId := "session123" blockedNumber := "0712345678" diff --git a/handlers/application/vouchers_test.go b/handlers/application/vouchers_test.go new file mode 100644 index 0000000..e380829 --- /dev/null +++ b/handlers/application/vouchers_test.go @@ -0,0 +1,282 @@ +package application + +import ( + "context" + "fmt" + "testing" + + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/sarafu-api/models" + "git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + "github.com/alecthomas/assert/v2" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" +) + +func TestManageVouchers(t *testing.T) { + sessionId := "session123" + publicKey := "0X13242618721" + + ctx, userStore := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Fatal(err) + } + flag_no_active_voucher, err := fm.GetFlag("flag_no_active_voucher") + if err != nil { + t.Fatal(err) + } + flag_api_error, err := fm.GetFlag("flag_api_call_error") + + if err != nil { + t.Fatal(err) + } + + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + vouchersResp []dataserviceapi.TokenHoldings + storedActiveVoucher string + expectedVoucherSymbols []byte + expectedUpdatedAddress []byte + expectedResult resource.Result + }{ + { + name: "No vouchers available", + vouchersResp: []dataserviceapi.TokenHoldings{}, + expectedVoucherSymbols: []byte(""), + expectedUpdatedAddress: []byte(""), + expectedResult: resource.Result{ + FlagSet: []uint32{flag_no_active_voucher}, + FlagReset: []uint32{flag_api_error}, + }, + }, + { + name: "Set default voucher when no active voucher is set", + vouchersResp: []dataserviceapi.TokenHoldings{ + { + TokenAddress: "0x123", + TokenSymbol: "TOKEN1", + TokenDecimals: "18", + Balance: "100", + }, + }, + expectedVoucherSymbols: []byte("1:TOKEN1"), + expectedUpdatedAddress: []byte("0x123"), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_api_error, flag_no_active_voucher}, + }, + }, + { + name: "Check and update active voucher balance", + vouchersResp: []dataserviceapi.TokenHoldings{ + {TokenAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, + {TokenAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, + }, + storedActiveVoucher: "SRF", + expectedVoucherSymbols: []byte("1:SRF\n2:MILO"), + expectedUpdatedAddress: []byte("0xd4c288865Ce"), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_api_error, flag_no_active_voucher}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockAccountService := new(mocks.MockAccountService) + + h := &MenuHandlers{ + userdataStore: userStore, + accountService: mockAccountService, + flagManager: fm, + logDb: logDb, + } + + mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil) + + // Store active voucher if needed + if tt.storedActiveVoucher != "" { + err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.storedActiveVoucher)) + if err != nil { + t.Fatal(err) + } + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte("0x41c188D45rfg6ds")) + if err != nil { + t.Fatal(err) + } + } + + res, err := h.ManageVouchers(ctx, "manage_vouchers", []byte("")) + assert.NoError(t, err) + assert.Equal(t, tt.expectedResult, res) + + if tt.storedActiveVoucher != "" { + // Validate stored voucher symbols + voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS) + assert.NoError(t, err) + assert.Equal(t, tt.expectedVoucherSymbols, voucherData) + + // Validate stored active contract address + updatedAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) + assert.NoError(t, err) + assert.Equal(t, tt.expectedUpdatedAddress, updatedAddress) + + mockAccountService.AssertExpectations(t) + } + }) + } +} + +func TestGetVoucherList(t *testing.T) { + sessionId := "session123" + + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + // Initialize MenuHandlers + h := &MenuHandlers{ + userdataStore: store, + ReplaceSeparatorFunc: mockReplaceSeparator, + } + + mockSymbols := []byte("1:SRF\n2:MILO") + mockBalances := []byte("1:10.099999\n2:40.7") + + // Put voucher symnols and balances data to the store + err := store.WriteEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS, mockSymbols) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_VOUCHER_BALANCES, mockBalances) + if err != nil { + t.Fatal(err) + } + + expectedList := []byte("1: SRF 10.09\n2: MILO 40.70") + + res, err := h.GetVoucherList(ctx, "", []byte("")) + + assert.NoError(t, err) + assert.Equal(t, res.Content, string(expectedList)) +} + +func TestViewVoucher(t *testing.T) { + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + ctx, store := InitializeTestStore(t) + sessionId := "session123" + ctx = context.WithValue(ctx, "SessionId", sessionId) + + h := &MenuHandlers{ + userdataStore: store, + flagManager: fm, + } + + // Define mock voucher data + mockData := map[storedb.DataTyp][]byte{ + storedb.DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"), + storedb.DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"), + storedb.DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"), + storedb.DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), + } + + // Put the data + for key, value := range mockData { + err := store.WriteEntry(ctx, sessionId, key, []byte(value)) + if err != nil { + t.Fatal(err) + } + } + + res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1")) + assert.NoError(t, err) + assert.Equal(t, res.Content, "Symbol: SRF\nBalance: 100") +} + +func TestSetVoucher(t *testing.T) { + ctx, store := InitializeTestStore(t) + sessionId := "session123" + + ctx = context.WithValue(ctx, "SessionId", sessionId) + + h := &MenuHandlers{ + userdataStore: store, + } + + // Define the temporary voucher data + tempData := &dataserviceapi.TokenHoldings{ + TokenSymbol: "SRF", + Balance: "200", + TokenDecimals: "6", + TokenAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + } + + expectedData := fmt.Sprintf("%s,%s,%s,%s", tempData.TokenSymbol, tempData.Balance, tempData.TokenDecimals, tempData.TokenAddress) + + // store the expectedData + if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(expectedData)); err != nil { + t.Fatal(err) + } + + res, err := h.SetVoucher(ctx, "set_voucher", []byte("")) + + assert.NoError(t, err) + + assert.Equal(t, string(tempData.TokenSymbol), res.Content) +} + +func TestGetVoucherDetails(t *testing.T) { + ctx, store := InitializeTestStore(t) + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + + flag_api_error, _ := fm.GetFlag("flag_api_call_error") + mockAccountService := new(mocks.MockAccountService) + + sessionId := "session123" + ctx = context.WithValue(ctx, "SessionId", sessionId) + expectedResult := resource.Result{} + + tokA_AAddress := "0x0000000000000000000000000000000000000000" + + h := &MenuHandlers{ + userdataStore: store, + flagManager: fm, + accountService: mockAccountService, + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte(tokA_AAddress)) + if err != nil { + t.Fatal(err) + } + tokenDetails := &models.VoucherDataResult{ + TokenName: "Token A", + TokenSymbol: "TOKA", + TokenLocation: "Kilifi,Kenya", + TokenCommodity: "Farming", + } + expectedResult.Content = fmt.Sprintf( + "Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", tokenDetails.TokenName, tokenDetails.TokenSymbol, tokenDetails.TokenCommodity, tokenDetails.TokenLocation, + ) + mockAccountService.On("VoucherData", string(tokA_AAddress)).Return(tokenDetails, nil) + res, err := h.GetVoucherDetails(ctx, "SessionId", []byte("")) + expectedResult.FlagReset = append(expectedResult.FlagReset, flag_api_error) + assert.NoError(t, err) + assert.Equal(t, expectedResult, res) +} -- 2.45.2 From 754008f0c10b3d41efc75272e9c398f0e24b49a7 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 4 Jul 2025 13:56:46 +0300 Subject: [PATCH 26/26] git ignore the user-data files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b523c77..69e9a58 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ cmd/.state/ id_* *.gdbm *.log +user-data -- 2.45.2