diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index 98864db..ca88978 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -29,10 +29,10 @@ import ( ) var ( - logg = logging.NewVanilla() - scriptDir = path.Join("services", "registration") - - build = "dev" + logg = logging.NewVanilla() + scriptDir = path.Join("services", "registration") + build = "dev" + menuSeparator = ": " ) func init() { @@ -130,9 +130,10 @@ func main() { pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ - Root: "root", - OutputSize: uint32(size), - FlagCount: uint32(128), + Root: "root", + OutputSize: uint32(size), + FlagCount: uint32(128), + MenuSeparator: menuSeparator, } if engineDebug { diff --git a/cmd/async/main.go b/cmd/async/main.go index e4c94b0..9cd04b3 100644 --- a/cmd/async/main.go +++ b/cmd/async/main.go @@ -23,6 +23,7 @@ import ( var ( logg = logging.NewVanilla() scriptDir = path.Join("services", "registration") + menuSeparator = ": " ) func init() { @@ -70,9 +71,10 @@ func main() { pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ - Root: "root", - OutputSize: uint32(size), - FlagCount: uint32(128), + Root: "root", + OutputSize: uint32(size), + FlagCount: uint32(128), + MenuSeparator: menuSeparator, } if engineDebug { diff --git a/cmd/http/main.go b/cmd/http/main.go index 96e2688..6ddfded 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -26,6 +26,7 @@ import ( var ( logg = logging.NewVanilla() scriptDir = path.Join("services", "registration") + menuSeparator = ": " ) func init() { @@ -58,9 +59,10 @@ func main() { pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ - Root: "root", - OutputSize: uint32(size), - FlagCount: uint32(128), + Root: "root", + OutputSize: uint32(size), + FlagCount: uint32(128), + MenuSeparator: menuSeparator, } if engineDebug { diff --git a/cmd/main.go b/cmd/main.go index 9599eb7..4fd084f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,8 +18,9 @@ import ( ) var ( - logg = logging.NewVanilla() - scriptDir = path.Join("services", "registration") + logg = logging.NewVanilla() + scriptDir = path.Join("services", "registration") + menuSeparator = ": " ) func init() { @@ -49,10 +50,11 @@ func main() { pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ - Root: "root", - SessionId: sessionId, - OutputSize: uint32(size), - FlagCount: uint32(128), + Root: "root", + SessionId: sessionId, + OutputSize: uint32(size), + FlagCount: uint32(128), + MenuSeparator: menuSeparator, } resourceDir := scriptDir diff --git a/common/transfer_statements.go b/common/transfer_statements.go index 4e6f66b..243ef4c 100644 --- a/common/transfer_statements.go +++ b/common/transfer_statements.go @@ -84,18 +84,18 @@ func GetTransferData(ctx context.Context, db storage.PrefixDb, publicKey string, // Adjust for 0-based indexing i := index - 1 - transactionType := "received" - party := fmt.Sprintf("from: %s", strings.TrimSpace(senders[i])) + transactionType := "Received" + party := fmt.Sprintf("From: %s", strings.TrimSpace(senders[i])) if strings.TrimSpace(senders[i]) == publicKey { - transactionType = "sent" - party = fmt.Sprintf("to: %s", strings.TrimSpace(recipients[i])) + transactionType = "Sent" + party = fmt.Sprintf("To: %s", strings.TrimSpace(recipients[i])) } formattedDate := formatDate(strings.TrimSpace(dates[i])) // Build the full transaction detail detail := fmt.Sprintf( - "%s %s %s\n%s\ncontract address: %s\ntxhash: %s\ndate: %s", + "%s %s %s\n%s\nContract address: %s\nTxhash: %s\nDate: %s", transactionType, strings.TrimSpace(values[i]), strings.TrimSpace(syms[i]), diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index a14cf59..1da28c3 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -2,6 +2,7 @@ package handlers import ( "context" + "strings" "git.defalsify.org/vise.git/asm" "git.defalsify.org/vise.git/db" @@ -64,7 +65,11 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) { } func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceInterface) (*ussd.Handlers, error) { - ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService) + replaceSeparatorFunc := func(input string) string { + return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator) + } + + ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService, replaceSeparatorFunc) if err != nil { return nil, err } @@ -111,7 +116,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher) ls.DbRs.AddLocalFunc("get_voucher_details", ussdHandlers.GetVoucherDetails) ls.DbRs.AddLocalFunc("reset_valid_pin", ussdHandlers.ResetValidPin) - ls.DbRs.AddLocalFunc("check_pin_mismatch", ussdHandlers.CheckPinMisMatch) + ls.DbRs.AddLocalFunc("check_pin_mismatch", ussdHandlers.CheckBlockedNumPinMisMatch) ls.DbRs.AddLocalFunc("validate_blocked_number", ussdHandlers.ValidateBlockedNumber) ls.DbRs.AddLocalFunc("retrieve_blocked_number", ussdHandlers.RetrieveBlockedNumber) ls.DbRs.AddLocalFunc("reset_unregistered_number", ussdHandlers.ResetUnregisteredNumber) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index f509ff4..a716f67 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -39,7 +39,7 @@ const ( pinPattern = `^\d{4}$` ) -// isValidPIN checks whether the given input is a 4 digit number +// checks whether the given input is a 4 digit number func isValidPIN(pin string) bool { match, _ := regexp.MatchString(pinPattern, pin) return match @@ -69,18 +69,20 @@ func (fm *FlagManager) GetFlag(label string) (uint32, error) { } type Handlers struct { - pe *persist.Persister - st *state.State - ca cache.Memory - userdataStore common.DataStore - adminstore *utils.AdminStore - flagManager *asm.FlagParser - accountService remote.AccountServiceInterface - prefixDb storage.PrefixDb - profile *models.Profile + pe *persist.Persister + st *state.State + ca cache.Memory + userdataStore common.DataStore + adminstore *utils.AdminStore + flagManager *asm.FlagParser + accountService remote.AccountServiceInterface + prefixDb storage.PrefixDb + profile *models.Profile + ReplaceSeparatorFunc func(string) string } -func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface) (*Handlers, error) { +// NewHandlers creates a new instance of the Handlers struct with the provided dependencies. +func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface, replaceSeparatorFunc func(string) string) (*Handlers, error) { if userdataStore == nil { return nil, fmt.Errorf("cannot create handler with nil userdata store") } @@ -93,16 +95,18 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util prefixDb := storage.NewSubPrefixDb(userdataStore, prefix) h := &Handlers{ - userdataStore: userDb, - flagManager: appFlags, - adminstore: adminstore, - accountService: accountService, - prefixDb: prefixDb, - profile: &models.Profile{Max: 6}, + userdataStore: userDb, + flagManager: appFlags, + adminstore: adminstore, + accountService: accountService, + prefixDb: prefixDb, + profile: &models.Profile{Max: 6}, + ReplaceSeparatorFunc: replaceSeparatorFunc, } return h, nil } +// WithPersister sets persister instance to the handlers. func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers { if h.pe != nil { panic("persister already set") @@ -111,6 +115,7 @@ func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers { return h } +// Init initializes the handler for a new session. func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) { var r resource.Result if h.pe == nil { @@ -154,7 +159,7 @@ func (h *Handlers) Exit() { h.pe = nil } -// SetLanguage sets the language across the menu +// SetLanguage sets the language across the menu. func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -178,6 +183,7 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r 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 *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error { flag_account_created, _ := h.flagManager.GetFlag("flag_account_created") r, err := h.accountService.CreateAccount(ctx) @@ -210,9 +216,9 @@ func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, r return nil } -// CreateAccount checks if any account exists on the JSON data file, and if not +// 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 +// sets the default values and flags. func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -236,19 +242,22 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte) return res, nil } -func (h *Handlers) CheckPinMisMatch(ctx context.Context, sym string, input []byte) (resource.Result, error) { +// CheckBlockedNumPinMisMatch checks if the provided PIN matches a temporary PIN stored for a blocked number. +func (h *Handlers) CheckBlockedNumPinMisMatch(ctx context.Context, sym string, input []byte) (resource.Result, error) { res := resource.Result{} flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") sessionId, ok := ctx.Value("SessionId").(string) if !ok { return res, fmt.Errorf("missing session") } + // Get blocked number from storage. store := h.userdataStore blockedNumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER) if err != nil { logg.ErrorCtxf(ctx, "failed to read blockedNumber entry with", "key", common.DATA_BLOCKED_NUMBER, "error", err) return res, err } + // Get temporary PIN for the blocked number. temporaryPin, err := store.ReadEntry(ctx, string(blockedNumber), common.DATA_TEMPORARY_VALUE) if err != nil { logg.ErrorCtxf(ctx, "failed to read temporaryPin entry with", "key", common.DATA_TEMPORARY_VALUE, "error", err) @@ -262,6 +271,7 @@ func (h *Handlers) CheckPinMisMatch(ctx context.Context, sym string, input []byt return res, nil } +// VerifyNewPin checks if a new PIN meets the required format criteria. func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { res := resource.Result{} _, ok := ctx.Value("SessionId").(string) @@ -270,7 +280,7 @@ func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) ( } flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin") pinInput := string(input) - // Validate that the PIN is a 4-digit number + // Validate that the PIN is a 4-digit number. if isValidPIN(pinInput) { res.FlagSet = append(res.FlagSet, flag_valid_pin) } else { @@ -280,9 +290,9 @@ func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) ( return res, nil } -// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_VALUE +// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_VALUE, // during the account creation process -// and during the change PIN process +// and during the change PIN process. func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -295,7 +305,7 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") accountPIN := string(input) - // Validate that the PIN is a 4-digit number + // Validate that the PIN is a 4-digit number. if !isValidPIN(accountPIN) { res.FlagSet = append(res.FlagSet, flag_incorrect_pin) return res, nil @@ -311,6 +321,7 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt return res, nil } +// SaveOthersTemporaryPin allows authorized users to set temporary PINs for blocked numbers. func (h *Handlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -321,12 +332,14 @@ func (h *Handlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input return res, fmt.Errorf("missing session") } temporaryPin := string(input) + // First, we retrieve the blocked number associated with this session blockedNumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER) if err != nil { logg.ErrorCtxf(ctx, "failed to read blockedNumber entry with", "key", common.DATA_BLOCKED_NUMBER, "error", err) return res, err } + // Then we save the temporary PIN for that blocked number err = store.WriteEntry(ctx, string(blockedNumber), common.DATA_TEMPORARY_VALUE, []byte(temporaryPin)) if err != nil { logg.ErrorCtxf(ctx, "failed to write temporaryPin entry with", "key", common.DATA_TEMPORARY_VALUE, "value", temporaryPin, "error", err) @@ -336,6 +349,7 @@ func (h *Handlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input return res, nil } +// ConfirmPinChange validates user's new PIN. If input matches the temporary PIN, saves it as the new account PIN. func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) @@ -355,6 +369,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt } else { res.FlagSet = append(res.FlagSet, flag_pin_mismatch) } + // If matched, save the confirmed PIN as the new account PIN err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(temporaryPin)) if err != nil { logg.ErrorCtxf(ctx, "failed to write temporaryPin entry with", "key", common.DATA_ACCOUNT_PIN, "value", temporaryPin, "error", err) @@ -365,7 +380,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt // 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 +// to access the main menu. func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -400,7 +415,7 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte return res, nil } -// codeFromCtx retrieves language codes from the context that can be used for handling translations +// retrieves language codes from the context that can be used for handling translations. func codeFromCtx(ctx context.Context) string { var code string if ctx.Value("Language") != nil { @@ -734,7 +749,7 @@ func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []by return res, nil } -// Setback sets the flag_back_set flag when the navigation is back +// Setback sets the flag_back_set flag when the navigation is back. func (h *Handlers) SetBack(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result //TODO: @@ -747,7 +762,7 @@ func (h *Handlers) SetBack(ctx context.Context, sym string, input []byte) (resou } // CheckAccountStatus queries the API using the TrackingId and sets flags -// based on the account status +// based on the account status. func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -787,7 +802,7 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b return res, nil } -// Quit displays the Thank you message and exits the menu +// Quit displays the Thank you message and exits the menu. func (h *Handlers) Quit(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -802,7 +817,7 @@ func (h *Handlers) Quit(ctx context.Context, sym string, input []byte) (resource return res, nil } -// QuitWithHelp displays helpline information then exits the menu +// QuitWithHelp displays helpline information then exits the menu. func (h *Handlers) QuitWithHelp(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -817,7 +832,7 @@ func (h *Handlers) QuitWithHelp(ctx context.Context, sym string, input []byte) ( return res, nil } -// VerifyYob verifies the length of the given input +// VerifyYob verifies the length of the given input. func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -839,7 +854,7 @@ func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (res return res, nil } -// ResetIncorrectYob resets the incorrect date format flag after a new attempt +// ResetIncorrectYob resets the incorrect date format flag after a new attempt. func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -849,7 +864,7 @@ func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []by } // CheckBalance retrieves the balance of the active voucher and sets -// the balance as the result content +// the balance as the result content. func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -899,9 +914,12 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) ( return res, nil } +// FetchCommunityBalance retrieves and displays the balance for community accounts in user's preferred language. func (h *Handlers) 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: @@ -910,6 +928,10 @@ func (h *Handlers) FetchCommunityBalance(ctx context.Context, sym string, input return res, nil } +// ResetOthersPin handles the PIN reset process for other users' accounts by: +// 1. Retrieving the blocked phone number from the session +// 2. Fetching the temporary PIN associated with that number +// 3. Updating the account PIN with the temporary PIN func (h *Handlers) ResetOthersPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result store := h.userdataStore @@ -935,6 +957,8 @@ func (h *Handlers) ResetOthersPin(ctx context.Context, sym string, input []byte) return res, 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 *Handlers) ResetUnregisteredNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number") @@ -942,6 +966,8 @@ func (h *Handlers) ResetUnregisteredNumber(ctx context.Context, sym string, inpu return res, nil } +// ValidateBlockedNumber performs validation of phone numbers, specifically for blocked numbers in the system. +// It checks phone number format and verifies registration status. func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -1070,7 +1096,7 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by } // TransactionReset resets the previous transaction data (Recipient and Amount) -// as well as the invalid flags +// as well as the invalid flags. func (h *Handlers) TransactionReset(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -1123,7 +1149,7 @@ func (h *Handlers) InviteValidRecipient(ctx context.Context, sym string, input [ return res, nil } -// ResetTransactionAmount resets the transaction amount and invalid flag +// ResetTransactionAmount resets the transaction amount and invalid flag. func (h *Handlers) ResetTransactionAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -1253,7 +1279,7 @@ func (h *Handlers) RetrieveBlockedNumber(ctx context.Context, sym string, input return res, nil } -// GetSender returns the sessionId (phoneNumber) +// GetSender returns the sessionId (phoneNumber). func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -1267,7 +1293,7 @@ func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (res return res, nil } -// GetAmount retrieves the amount from teh Gdbm Db +// GetAmount retrieves the amount from teh Gdbm Db. func (h *Handlers) GetAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -1291,7 +1317,7 @@ func (h *Handlers) GetAmount(ctx context.Context, sym string, input []byte) (res return res, nil } -// InitiateTransaction calls the TokenTransfer and returns a confirmation based on the result +// InitiateTransaction calls the TokenTransfer and returns a confirmation based on the result. func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -1341,6 +1367,8 @@ func (h *Handlers) InitiateTransaction(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 *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var profileInfo []byte @@ -1360,6 +1388,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input if !ok { return res, fmt.Errorf("missing session") } + // Extract the field name from the state machine position. sm, _ := h.st.Where() parts := strings.SplitN(sm, "_", 2) filename := parts[1] @@ -1452,6 +1481,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input return res, nil } +// GetProfileInfo provides a comprehensive view of a user's profile. func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var defaultValue string @@ -1520,7 +1550,7 @@ func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte) } // SetDefaultVoucher retrieves the current vouchers -// and sets the first as the default voucher, if no active voucher is set +// and sets the first as the default voucher, if no active voucher is set. func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -1605,7 +1635,7 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by } // CheckVouchers retrieves the token holdings from the API using the "PublicKey" and stores -// them to gdbm +// them to gdbm. func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) @@ -1677,7 +1707,7 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) return res, nil } -// GetVoucherList fetches the list of vouchers and formats them +// GetVoucherList fetches the list of vouchers and formats them. func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -1688,13 +1718,15 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) return res, err } - res.Content = string(voucherData) + formattedData := h.ReplaceSeparatorFunc(string(voucherData)) + + 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 +// and displays it to the user for them to select it. func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) @@ -1735,7 +1767,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r return res, nil } -// SetVoucher retrieves the temp voucher data and sets it as the active data +// SetVoucher retrieves the temp voucher data and sets it as the active data. func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -1761,7 +1793,7 @@ func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (re return res, nil } -// GetVoucherDetails retrieves the voucher details +// GetVoucherDetails retrieves the voucher details. func (h *Handlers) GetVoucherDetails(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result store := h.userdataStore @@ -1793,7 +1825,7 @@ func (h *Handlers) GetVoucherDetails(ctx context.Context, sym string, input []by return res, nil } -// CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb +// CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb. func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) @@ -1851,13 +1883,14 @@ func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []by return res, nil } -// GetTransactionsList fetches the list of transactions and formats them +// GetTransactionsList fetches the list of transactions and formats them. func (h *Handlers) 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") } + store := h.userdataStore publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY) if err != nil { @@ -1900,12 +1933,14 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input [] value := strings.TrimSpace(values[i]) date := strings.Split(strings.TrimSpace(dates[i]), " ")[0] - status := "received" + status := "Received" if sender == string(publicKey) { - status = "sent" + status = "Sent" } - formattedTransactions = append(formattedTransactions, fmt.Sprintf("%d:%s %s %s %s", i+1, status, value, sym, date)) + // 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") @@ -1914,7 +1949,7 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input [] } // ViewTransactionStatement retrieves the transaction statement -// and displays it to the user +// and displays it to the user. func (h *Handlers) ViewTransactionStatement(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) @@ -1962,6 +1997,7 @@ func (h *Handlers) ViewTransactionStatement(ctx context.Context, sym string, inp return res, nil } +// handles bulk updates of profile information. func (h *Handlers) insertProfileItems(ctx context.Context, sessionId string, res *resource.Result) error { var err error store := h.userdataStore @@ -1998,7 +2034,7 @@ func (h *Handlers) insertProfileItems(ctx context.Context, sessionId string, res return nil } -// UpdateAllProfileItems is used to persist all the new profile information and setup the required profile flags +// UpdateAllProfileItems is used to persist all the new profile information and setup the required profile flags. func (h *Handlers) UpdateAllProfileItems(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 25470e8..c01678d 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "path" + "strings" "testing" "git.defalsify.org/vise.git/lang" @@ -32,6 +33,11 @@ var ( flagsPath = path.Join(baseDir, "services", "registration", "pp.csv") ) +// mockReplaceSeparator function +var mockReplaceSeparator = func(input string) string { + return strings.ReplaceAll(input, ":", ": ") +} + // InitializeTestStore sets up and returns an in-memory database and store. func InitializeTestStore(t *testing.T) (context.Context, *common.UserDataStore) { ctx := context.Background() @@ -67,12 +73,15 @@ func TestNewHandlers(t *testing.T) { _, store := InitializeTestStore(t) fm, err := NewFlagManager(flagsPath) - accountService := testservice.TestAccountService{} if err != nil { - t.Logf(err.Error()) + log.Fatal(err) } + + accountService := testservice.TestAccountService{} + + // Test case for valid UserDataStore t.Run("Valid UserDataStore", func(t *testing.T) { - handlers, err := NewHandlers(fm.parser, store, nil, &accountService) + handlers, err := NewHandlers(fm.parser, store, nil, &accountService, mockReplaceSeparator) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -82,19 +91,30 @@ func TestNewHandlers(t *testing.T) { if handlers.userdataStore == nil { t.Fatal("expected userdataStore to be set in handlers") } + if handlers.ReplaceSeparatorFunc == nil { + t.Fatal("expected ReplaceSeparatorFunc to be set in handlers") + } + + // Test ReplaceSeparatorFunc functionality + input := "1:Menu item" + expectedOutput := "1: Menu item" + if handlers.ReplaceSeparatorFunc(input) != expectedOutput { + t.Fatalf("ReplaceSeparatorFunc function did not return expected output: got %v, want %v", handlers.ReplaceSeparatorFunc(input), expectedOutput) + } }) - // Test case for nil userdataStore + // Test case for nil UserDataStore t.Run("Nil UserDataStore", func(t *testing.T) { - handlers, err := NewHandlers(fm.parser, nil, nil, &accountService) + handlers, err := NewHandlers(fm.parser, nil, nil, &accountService, mockReplaceSeparator) if err == nil { t.Fatal("expected an error, got none") } if handlers != nil { t.Fatal("expected handlers to be nil") } - if err.Error() != "cannot create handler with nil userdata store" { - t.Fatalf("expected specific error, got %v", err) + expectedError := "cannot create handler with nil userdata store" + if err.Error() != expectedError { + t.Fatalf("expected error '%s', got '%v'", expectedError, err) } }) } @@ -1982,26 +2002,31 @@ func TestCheckVouchers(t *testing.T) { func TestGetVoucherList(t *testing.T) { sessionId := "session123" + ctx := context.WithValue(context.Background(), "SessionId", sessionId) spdb := InitializeTestSubPrefixDb(t, ctx) + // Initialize Handlers h := &Handlers{ - prefixDb: spdb, + prefixDb: spdb, + ReplaceSeparatorFunc: mockReplaceSeparator, } - expectedSym := []byte("1:SRF\n2:MILO") + mockSyms := []byte("1:SRF\n2:MILO") // Put voucher sym data from the store - err := spdb.Put(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS), expectedSym) + err := spdb.Put(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS), mockSyms) if err != nil { t.Fatal(err) } + expectedSyms := []byte("1: SRF\n2: MILO") + res, err := h.GetVoucherList(ctx, "", []byte("")) assert.NoError(t, err) - assert.Equal(t, res.Content, string(expectedSym)) + assert.Equal(t, res.Content, string(expectedSyms)) } func TestViewVoucher(t *testing.T) {