From 3ef64271e7429d00012c579155a20c97784ec38d Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 25 Oct 2024 17:24:09 +0300 Subject: [PATCH 01/15] Store and retrieve the voucher decimals and contract address --- internal/handlers/ussd/menuhandler.go | 284 +++++++++++++++++--------- internal/utils/db.go | 4 + 2 files changed, 187 insertions(+), 101 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index f4d279b..ec33ce6 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -57,6 +57,14 @@ func (fm *FlagManager) GetFlag(label string) (uint32, error) { return fm.parser.GetFlag(label) } +// VoucherMetadata helps organize voucher data fields +type VoucherMetadata struct { + Symbol string + Balance string + Decimal string + Address string +} + type Handlers struct { pe *persist.Persister st *state.State @@ -1094,41 +1102,49 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) return res, nil } - // process voucher data - voucherSymbolList, voucherBalanceList := ProcessVouchers(vouchersResp.Result.Holdings) - prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers")) - err = prefixdb.Put(ctx, []byte("sym"), []byte(voucherSymbolList)) - if err != nil { - return res, nil + data := processVouchers(vouchersResp.Result.Holdings) + + // Store all voucher data + dataMap := map[string]string{ + "sym": data.Symbol, + "bal": data.Balance, + "deci": data.Decimal, + "addr": data.Address, } - err = prefixdb.Put(ctx, []byte("bal"), []byte(voucherBalanceList)) - if err != nil { - return res, nil + for key, value := range dataMap { + if err := prefixdb.Put(ctx, []byte(key), []byte(value)); err != nil { + return res, nil + } } return res, nil } -// ProcessVouchers formats the holdings into symbol and balance lists. -func ProcessVouchers(holdings []struct { +// processVouchers converts holdings into formatted strings +func processVouchers(holdings []struct { ContractAddress string `json:"contractAddress"` TokenSymbol string `json:"tokenSymbol"` TokenDecimals string `json:"tokenDecimals"` Balance string `json:"balance"` -}) (string, string) { - var numberedSymbols, numberedBalances []string +}) VoucherMetadata { + var data VoucherMetadata + var symbols, balances, decimals, addresses []string - for i, voucher := range holdings { - numberedSymbols = append(numberedSymbols, fmt.Sprintf("%d:%s", i+1, voucher.TokenSymbol)) - numberedBalances = append(numberedBalances, fmt.Sprintf("%d:%s", i+1, voucher.Balance)) + for i, h := range holdings { + symbols = append(symbols, fmt.Sprintf("%d:%s", i+1, h.TokenSymbol)) + balances = append(balances, fmt.Sprintf("%d:%s", i+1, h.Balance)) + decimals = append(decimals, fmt.Sprintf("%d:%s", i+1, h.TokenDecimals)) + addresses = append(addresses, fmt.Sprintf("%d:%s", i+1, h.ContractAddress)) } - voucherSymbolList := strings.Join(numberedSymbols, "\n") - voucherBalanceList := strings.Join(numberedBalances, "\n") + data.Symbol = strings.Join(symbols, "\n") + data.Balance = strings.Join(balances, "\n") + data.Decimal = strings.Join(decimals, "\n") + data.Address = strings.Join(addresses, "\n") - return voucherSymbolList, voucherBalanceList + return data } // GetVoucherList fetches the list of vouchers and formats them @@ -1162,7 +1178,6 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") inputStr := string(input) - if inputStr == "0" || inputStr == "99" { res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) return res, nil @@ -1170,118 +1185,185 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers")) - // Retrieve the voucher symbol list - voucherSymbolList, err := prefixdb.Get(ctx, []byte("sym")) + // Retrieve the voucher metadata + metadata, err := getVoucherData(ctx, prefixdb, inputStr) if err != nil { - return res, fmt.Errorf("failed to retrieve voucher symbol list: %v", err) + return res, fmt.Errorf("failed to retrieve voucher data: %v", err) } - // Retrieve the voucher balance list - voucherBalanceList, err := prefixdb.Get(ctx, []byte("bal")) - if err != nil { - return res, fmt.Errorf("failed to retrieve voucher balance list: %v", err) - } - - // match the voucher symbol and balance with the input - matchedSymbol, matchedBalance := MatchVoucher(inputStr, string(voucherSymbolList), string(voucherBalanceList)) - - // If a match is found, write the temporary sym and balance - if matchedSymbol != "" && matchedBalance != "" { - err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte(matchedSymbol)) - if err != nil { - return res, err - } - err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte(matchedBalance)) - if err != nil { - return res, err - } - - res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) - res.Content = fmt.Sprintf("%s\n%s", matchedSymbol, matchedBalance) - } else { + if metadata == nil { res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) + return res, nil } + if err := h.storeTemporaryVoucher(ctx, sessionId, metadata); err != nil { + return resource.Result{}, err + } + + res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) + res.Content = fmt.Sprintf("%s\n%s", metadata.Symbol, metadata.Balance) + return res, nil } -// MatchVoucher finds the matching voucher symbol and balance based on the input. -func MatchVoucher(inputStr string, voucherSymbols, voucherBalances string) (string, string) { - // Split the lists into slices for processing - symbols := strings.Split(voucherSymbols, "\n") - balances := strings.Split(voucherBalances, "\n") +// getVoucherData retrieves and matches voucher data +func getVoucherData(ctx context.Context, db *storage.SubPrefixDb, input string) (*VoucherMetadata, error) { + keys := []string{"sym", "bal", "deci", "addr"} + data := make(map[string]string) - var matchedSymbol, matchedBalance string + for _, key := range keys { + value, err := db.Get(ctx, []byte(key)) + if err != nil { + return nil, fmt.Errorf("failed to get %s: %v", key, err) + } + data[key] = string(value) + } - for i, symbol := range symbols { - symbolParts := strings.SplitN(symbol, ":", 2) - if len(symbolParts) != 2 { + symbol, balance, decimal, address := matchVoucher(input, + data["sym"], + data["bal"], + data["deci"], + data["addr"]) + + if symbol == "" { + return nil, nil + } + + return &VoucherMetadata{ + Symbol: symbol, + Balance: balance, + Decimal: decimal, + Address: address, + }, nil +} + +// MatchVoucher finds the matching voucher symbol, balance, decimals and contract address based on the input. +func matchVoucher(input, symbols, balances, decimals, addresses string) (symbol, balance, decimal, address string) { + symList := strings.Split(symbols, "\n") + balList := strings.Split(balances, "\n") + decList := strings.Split(decimals, "\n") + addrList := strings.Split(addresses, "\n") + + for i, sym := range symList { + parts := strings.SplitN(sym, ":", 2) + if len(parts) != 2 { continue } - voucherNum := symbolParts[0] - voucherSymbol := symbolParts[1] - // Check if input matches either the number or the symbol - if inputStr == voucherNum || strings.EqualFold(inputStr, voucherSymbol) { - matchedSymbol = voucherSymbol - // Ensure there's a corresponding balance - if i < len(balances) { - matchedBalance = strings.SplitN(balances[i], ":", 2)[1] + if input == parts[0] || strings.EqualFold(input, parts[1]) { + symbol = parts[1] + if i < len(balList) { + balance = strings.SplitN(balList[i], ":", 2)[1] + } + if i < len(decList) { + decimal = strings.SplitN(decList[i], ":", 2)[1] + } + if i < len(addrList) { + address = strings.SplitN(addrList[i], ":", 2)[1] } break } } - - return matchedSymbol, matchedBalance + return +} + +func (h *Handlers) storeTemporaryVoucher(ctx context.Context, sessionId string, data *VoucherMetadata) error { + entries := map[utils.DataTyp][]byte{ + utils.DATA_TEMPORARY_SYM: []byte(data.Symbol), + utils.DATA_TEMPORARY_BAL: []byte(data.Balance), + utils.DATA_TEMPORARY_DECIMAL: []byte(data.Decimal), + utils.DATA_TEMPORARY_ADDRESS: []byte(data.Address), + } + + for key, value := range entries { + if err := h.userdataStore.WriteEntry(ctx, sessionId, key, value); err != nil { + return err + } + } + return nil } -// SetVoucher retrieves the temporary sym and balance, -// sets them as the active data and -// clears the temporary data func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error - store := h.userdataStore - + sessionId, ok := ctx.Value("SessionId").(string) if !ok { return res, fmt.Errorf("missing session") } - // get the current temporary symbol - temporarySym, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_SYM) + // Get temporary data + tempData, err := h.getTemporaryVoucherData(ctx, sessionId) if err != nil { - return res, err - } - // get the current temporary balance - temporaryBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_BAL) - if err != nil { - return res, err + return resource.Result{}, err } - // set the active symbol - err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(temporarySym)) - if err != nil { - return res, err - } - // set the active balance - err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL, []byte(temporaryBal)) - if err != nil { - return res, err + // Set as active and clear temporary + if err := h.updateVoucherData(ctx, sessionId, tempData); err != nil { + return resource.Result{}, err } - // reset the temporary symbol - err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte("")) - if err != nil { - return res, err - } - // reset the temporary balance - err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte("")) - if err != nil { - return res, err - } - - res.Content = string(temporarySym) - - return res, nil + return resource.Result{Content: tempData.Symbol}, nil +} + +func (h *Handlers) getTemporaryVoucherData(ctx context.Context, sessionId string) (*VoucherMetadata, error) { + store := h.userdataStore + + keys := []utils.DataTyp{ + utils.DATA_TEMPORARY_SYM, + utils.DATA_TEMPORARY_BAL, + utils.DATA_TEMPORARY_DECIMAL, + utils.DATA_TEMPORARY_ADDRESS, + } + + data := &VoucherMetadata{} + values := make([][]byte, len(keys)) + + for i, key := range keys { + value, err := store.ReadEntry(ctx, sessionId, key) + if err != nil { + return nil, err + } + values[i] = value + } + + data.Symbol = string(values[0]) + data.Balance = string(values[1]) + data.Decimal = string(values[2]) + data.Address = string(values[3]) + + return data, nil +} + +func (h *Handlers) updateVoucherData(ctx context.Context, sessionId string, data *VoucherMetadata) error { + // Set active voucher data + activeEntries := map[utils.DataTyp][]byte{ + utils.DATA_ACTIVE_SYM: []byte(data.Symbol), + utils.DATA_ACTIVE_BAL: []byte(data.Balance), + utils.DATA_ACTIVE_DECIMAL: []byte(data.Decimal), + utils.DATA_ACTIVE_ADDRESS: []byte(data.Address), + } + + // Clear temporary voucher data + tempEntries := map[utils.DataTyp][]byte{ + utils.DATA_TEMPORARY_SYM: []byte(""), + utils.DATA_TEMPORARY_BAL: []byte(""), + utils.DATA_TEMPORARY_DECIMAL: []byte(""), + utils.DATA_TEMPORARY_ADDRESS: []byte(""), + } + + // Write all entries + for key, value := range activeEntries { + if err := h.userdataStore.WriteEntry(ctx, sessionId, key, value); err != nil { + return err + } + } + + for key, value := range tempEntries { + if err := h.userdataStore.WriteEntry(ctx, sessionId, key, value); err != nil { + return err + } + } + + return nil } diff --git a/internal/utils/db.go b/internal/utils/db.go index 45e7681..3c2c81e 100644 --- a/internal/utils/db.go +++ b/internal/utils/db.go @@ -28,6 +28,10 @@ const ( DATA_ACTIVE_SYM DATA_TEMPORARY_BAL DATA_ACTIVE_BAL + DATA_TEMPORARY_DECIMAL + DATA_ACTIVE_DECIMAL + DATA_TEMPORARY_ADDRESS + DATA_ACTIVE_ADDRESS ) func typToBytes(typ DataTyp) []byte { From ddae746b9d3e0e9b9470040e48d210de0d65ceeb Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 28 Oct 2024 11:07:59 +0300 Subject: [PATCH 02/15] formatted code --- internal/handlers/ussd/menuhandler.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index ec33ce6..1385261 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1219,10 +1219,10 @@ func getVoucherData(ctx context.Context, db *storage.SubPrefixDb, input string) data[key] = string(value) } - symbol, balance, decimal, address := matchVoucher(input, - data["sym"], - data["bal"], - data["deci"], + symbol, balance, decimal, address := matchVoucher(input, + data["sym"], + data["bal"], + data["deci"], data["addr"]) if symbol == "" { @@ -1286,7 +1286,7 @@ func (h *Handlers) storeTemporaryVoucher(ctx context.Context, sessionId string, func (h *Handlers) SetVoucher(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") @@ -1308,7 +1308,7 @@ func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (re func (h *Handlers) getTemporaryVoucherData(ctx context.Context, sessionId string) (*VoucherMetadata, error) { store := h.userdataStore - + keys := []utils.DataTyp{ utils.DATA_TEMPORARY_SYM, utils.DATA_TEMPORARY_BAL, From dc782d87a8a0cf3e324fc9f3ce7b93b01a420803 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 28 Oct 2024 11:20:06 +0300 Subject: [PATCH 03/15] Updated the TestSetVoucher --- internal/handlers/ussd/menuhandler.go | 9 +++--- internal/handlers/ussd/menuhandler_test.go | 37 +++++++++++++++++++--- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 1385261..f04b3f8 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1197,7 +1197,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r } if err := h.storeTemporaryVoucher(ctx, sessionId, metadata); err != nil { - return resource.Result{}, err + return res, err } res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) @@ -1295,15 +1295,16 @@ func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (re // Get temporary data tempData, err := h.getTemporaryVoucherData(ctx, sessionId) if err != nil { - return resource.Result{}, err + return res, err } // Set as active and clear temporary if err := h.updateVoucherData(ctx, sessionId, tempData); err != nil { - return resource.Result{}, err + return res, err } - return resource.Result{Content: tempData.Symbol}, nil + res.Content = tempData.Symbol + return res, nil } func (h *Handlers) getTemporaryVoucherData(ctx context.Context, sessionId string) (*VoucherMetadata, error) { diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 9bcee63..b53a289 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -1816,16 +1816,43 @@ func TestSetVoucher(t *testing.T) { sessionId := "session123" ctx := context.WithValue(context.Background(), "SessionId", sessionId) + // Define the temporary voucher data temporarySym := []byte("tempSym") temporaryBal := []byte("tempBal") + temporaryDecimal := []byte("2") + temporaryAddress := []byte("0x123456") - // Set expectations for the mock data store + // Define the expected active entries + activeEntries := map[utils.DataTyp][]byte{ + utils.DATA_ACTIVE_SYM: temporarySym, + utils.DATA_ACTIVE_BAL: temporaryBal, + utils.DATA_ACTIVE_DECIMAL: temporaryDecimal, + utils.DATA_ACTIVE_ADDRESS: temporaryAddress, + } + + // Define the temporary entries to be cleared + tempEntries := map[utils.DataTyp][]byte{ + utils.DATA_TEMPORARY_SYM: []byte(""), + utils.DATA_TEMPORARY_BAL: []byte(""), + utils.DATA_TEMPORARY_DECIMAL: []byte(""), + utils.DATA_TEMPORARY_ADDRESS: []byte(""), + } + + // Mocking ReadEntry calls for temporary data retrieval mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM).Return(temporarySym, nil) mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL).Return(temporaryBal, nil) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_SYM, temporarySym).Return(nil) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_BAL, temporaryBal).Return(nil) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte("")).Return(nil) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte("")).Return(nil) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_DECIMAL).Return(temporaryDecimal, nil) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_ADDRESS).Return(temporaryAddress, nil) + + // Mocking WriteEntry calls for setting active data + for key, value := range activeEntries { + mockDataStore.On("WriteEntry", ctx, sessionId, key, value).Return(nil) + } + + // Mocking WriteEntry calls for clearing temporary data + for key, value := range tempEntries { + mockDataStore.On("WriteEntry", ctx, sessionId, key, value).Return(nil) + } h := &Handlers{ userdataStore: mockDataStore, From 5d294b663cc1fc3621eaba97b9622f41b44021a5 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 28 Oct 2024 16:21:31 +0300 Subject: [PATCH 04/15] Added the PrefixDb interface --- internal/storage/db.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/storage/db.go b/internal/storage/db.go index 060f0c0..8c9ff35 100644 --- a/internal/storage/db.go +++ b/internal/storage/db.go @@ -10,6 +10,14 @@ const ( DATATYPE_USERSUB = 64 ) +// PrefixDb interface abstracts the database operations. +type PrefixDb interface { + Get(ctx context.Context, key []byte) ([]byte, error) + Put(ctx context.Context, key []byte, val []byte) error +} + +var _ PrefixDb = (*SubPrefixDb)(nil) + type SubPrefixDb struct { store db.Db pfx []byte @@ -29,11 +37,7 @@ func (s *SubPrefixDb) toKey(k []byte) []byte { func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) { s.store.SetPrefix(DATATYPE_USERSUB) key = s.toKey(key) - v, err := s.store.Get(ctx, key) - if err != nil { - return nil, err - } - return v, nil + return s.store.Get(ctx, key) } func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error { From 520f5abdcdba081171b2ed2cdc835a91603c38d3 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 28 Oct 2024 16:23:44 +0300 Subject: [PATCH 05/15] Added the subPrefixDb mock --- internal/mocks/subprefixdbmock.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 internal/mocks/subprefixdbmock.go diff --git a/internal/mocks/subprefixdbmock.go b/internal/mocks/subprefixdbmock.go new file mode 100644 index 0000000..e98bcb6 --- /dev/null +++ b/internal/mocks/subprefixdbmock.go @@ -0,0 +1,21 @@ +package mocks + +import ( + "context" + + "github.com/stretchr/testify/mock" +) + +type MockSubPrefixDb struct { + mock.Mock +} + +func (m *MockSubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) { + args := m.Called(ctx, key) + return args.Get(0).([]byte), args.Error(1) +} + +func (m *MockSubPrefixDb) Put(ctx context.Context, key, val []byte) error { + args := m.Called(ctx, key, val) + return args.Error(0) +} From 131f3bcf46861d5424246b26ddb9f60515eba65a Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 28 Oct 2024 16:27:24 +0300 Subject: [PATCH 06/15] Added the prefixDb to the Handlers struct~ --- internal/handlers/ussd/menuhandler.go | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index f04b3f8..59377d4 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -72,6 +72,7 @@ type Handlers struct { userdataStore utils.DataStore flagManager *asm.FlagParser accountService server.AccountServiceInterface + prefixDb storage.PrefixDb } func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService server.AccountServiceInterface) (*Handlers, error) { @@ -81,10 +82,14 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService s userDb := &utils.UserDataStore{ Db: userdataStore, } + // Instantiate the SubPrefixDb with "vouchers" prefix + prefixDb := storage.NewSubPrefixDb(userdataStore, []byte("vouchers")) + h := &Handlers{ userdataStore: userDb, flagManager: appFlags, accountService: accountService, + prefixDb: prefixDb, } return h, nil } @@ -1102,7 +1107,6 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) return res, nil } - prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers")) data := processVouchers(vouchersResp.Result.Holdings) // Store all voucher data @@ -1114,7 +1118,7 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) } for key, value := range dataMap { - if err := prefixdb.Put(ctx, []byte(key), []byte(value)); err != nil { + if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil { return res, nil } } @@ -1152,12 +1156,9 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) var res resource.Result // Read vouchers from the store - store := h.userdataStore - prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers")) - - voucherData, err := prefixdb.Get(ctx, []byte("sym")) + voucherData, err := h.prefixDb.Get(ctx, []byte("sym")) if err != nil { - return res, nil + return res, err } res.Content = string(voucherData) @@ -1168,8 +1169,6 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) // ViewVoucher retrieves the token holding and balance from the subprefixDB func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result - store := h.userdataStore - sessionId, ok := ctx.Value("SessionId").(string) if !ok { return res, fmt.Errorf("missing session") @@ -1183,10 +1182,8 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r return res, nil } - prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers")) - - // Retrieve the voucher metadata - metadata, err := getVoucherData(ctx, prefixdb, inputStr) + // Retrieve the voucher metadata using the PrefixDb interface + metadata, err := getVoucherData(ctx, h.prefixDb, inputStr) if err != nil { return res, fmt.Errorf("failed to retrieve voucher data: %v", err) } @@ -1207,7 +1204,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r } // getVoucherData retrieves and matches voucher data -func getVoucherData(ctx context.Context, db *storage.SubPrefixDb, input string) (*VoucherMetadata, error) { +func getVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*VoucherMetadata, error) { keys := []string{"sym", "bal", "deci", "addr"} data := make(map[string]string) From 2c361e5b96fe98e5c64964676cb2e6d4b57b8aac Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 28 Oct 2024 16:30:13 +0300 Subject: [PATCH 07/15] Added tests related to the vouchers list --- internal/handlers/ussd/menuhandler_test.go | 518 ++++++++++++++++++--- 1 file changed, 455 insertions(+), 63 deletions(-) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index b53a289..801e44c 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -1480,8 +1480,8 @@ func TestValidateAmount(t *testing.T) { }, }, { - name: "Test with invalid amount format", - input: []byte("0.02ms"), + name: "Test with invalid amount format", + input: []byte("0.02ms"), activeBal: []byte("5"), expectedResult: resource.Result{ FlagSet: []uint32{flag_invalid_amount}, @@ -1810,67 +1810,6 @@ func TestConfirmPin(t *testing.T) { } } -func TestSetVoucher(t *testing.T) { - mockDataStore := new(mocks.MockUserDataStore) - - sessionId := "session123" - ctx := context.WithValue(context.Background(), "SessionId", sessionId) - - // Define the temporary voucher data - temporarySym := []byte("tempSym") - temporaryBal := []byte("tempBal") - temporaryDecimal := []byte("2") - temporaryAddress := []byte("0x123456") - - // Define the expected active entries - activeEntries := map[utils.DataTyp][]byte{ - utils.DATA_ACTIVE_SYM: temporarySym, - utils.DATA_ACTIVE_BAL: temporaryBal, - utils.DATA_ACTIVE_DECIMAL: temporaryDecimal, - utils.DATA_ACTIVE_ADDRESS: temporaryAddress, - } - - // Define the temporary entries to be cleared - tempEntries := map[utils.DataTyp][]byte{ - utils.DATA_TEMPORARY_SYM: []byte(""), - utils.DATA_TEMPORARY_BAL: []byte(""), - utils.DATA_TEMPORARY_DECIMAL: []byte(""), - utils.DATA_TEMPORARY_ADDRESS: []byte(""), - } - - // Mocking ReadEntry calls for temporary data retrieval - mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM).Return(temporarySym, nil) - mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL).Return(temporaryBal, nil) - mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_DECIMAL).Return(temporaryDecimal, nil) - mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_ADDRESS).Return(temporaryAddress, nil) - - // Mocking WriteEntry calls for setting active data - for key, value := range activeEntries { - mockDataStore.On("WriteEntry", ctx, sessionId, key, value).Return(nil) - } - - // Mocking WriteEntry calls for clearing temporary data - for key, value := range tempEntries { - mockDataStore.On("WriteEntry", ctx, sessionId, key, value).Return(nil) - } - - h := &Handlers{ - userdataStore: mockDataStore, - } - - // Call the method under test - res, err := h.SetVoucher(ctx, "someSym", []byte{}) - - // Assert that no errors occurred - assert.NoError(t, err) - - // Assert that the result content is correct - assert.Equal(t, string(temporarySym), res.Content) - - // Assert that expectations were met - mockDataStore.AssertExpectations(t) -} - func TestFetchCustodialBalances(t *testing.T) { fm, err := NewFlagManager(flagsPath) if err != nil { @@ -1952,3 +1891,456 @@ func TestFetchCustodialBalances(t *testing.T) { }) } } + +func TestSetDefaultVoucher(t *testing.T) { + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + + flag_no_active_voucher, err := fm.GetFlag("flag_no_active_voucher") + if err != nil { + t.Logf(err.Error()) + } + + // Define session ID and mock data + sessionId := "session123" + publicKey := "0X13242618721" + notFoundErr := db.ErrNotFound{} + ctx := context.WithValue(context.Background(), "SessionId", sessionId) + + tests := []struct { + name string + vouchersResp *models.VoucherHoldingResponse + expectedResult resource.Result + }{ + { + name: "Test set default voucher when no active voucher exists", + vouchersResp: &models.VoucherHoldingResponse{ + Ok: true, + Description: "Vouchers fetched successfully", + Result: struct { + Holdings []struct { + ContractAddress string `json:"contractAddress"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals string `json:"tokenDecimals"` + Balance string `json:"balance"` + } `json:"holdings"` + }{ + Holdings: []struct { + ContractAddress string `json:"contractAddress"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals string `json:"tokenDecimals"` + Balance string `json:"balance"` + }{ + { + ContractAddress: "0x123", + TokenSymbol: "TOKEN1", + TokenDecimals: "18", + Balance: "100", + }, + }, + }, + }, + expectedResult: resource.Result{}, + }, + { + name: "Test no vouchers available", + vouchersResp: &models.VoucherHoldingResponse{ + Ok: true, + Description: "No vouchers available", + Result: struct { + Holdings []struct { + ContractAddress string `json:"contractAddress"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals string `json:"tokenDecimals"` + Balance string `json:"balance"` + } `json:"holdings"` + }{ + Holdings: []struct { + ContractAddress string `json:"contractAddress"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals string `json:"tokenDecimals"` + Balance string `json:"balance"` + }{}, + }, + }, + expectedResult: resource.Result{ + FlagSet: []uint32{flag_no_active_voucher}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockDataStore := new(mocks.MockUserDataStore) + mockAccountService := new(mocks.MockAccountService) + + h := &Handlers{ + userdataStore: mockDataStore, + accountService: mockAccountService, + flagManager: fm.parser, + } + + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_ACTIVE_SYM).Return([]byte(""), notFoundErr) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil) + + mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil) + + if len(tt.vouchersResp.Result.Holdings) > 0 { + firstVoucher := tt.vouchersResp.Result.Holdings[0] + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(firstVoucher.TokenSymbol)).Return(nil) + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_BAL, []byte(firstVoucher.Balance)).Return(nil) + } + + res, err := h.SetDefaultVoucher(ctx, "set_default_voucher", []byte("some-input")) + + assert.NoError(t, err) + + assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result") + + mockDataStore.AssertExpectations(t) + mockAccountService.AssertExpectations(t) + }) + } +} + +func TestCheckVouchers(t *testing.T) { + mockDataStore := new(mocks.MockUserDataStore) + mockAccountService := new(mocks.MockAccountService) + mockSubPrefixDb := new(mocks.MockSubPrefixDb) + + sessionId := "session123" + publicKey := "0X13242618721" + + h := &Handlers{ + userdataStore: mockDataStore, + accountService: mockAccountService, + prefixDb: mockSubPrefixDb, + } + + ctx := context.WithValue(context.Background(), "SessionId", sessionId) + + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil) + + mockVouchersResponse := &models.VoucherHoldingResponse{} + mockVouchersResponse.Result.Holdings = []struct { + ContractAddress string `json:"contractAddress"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals string `json:"tokenDecimals"` + Balance string `json:"balance"` + }{ + {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, + {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, + } + + mockAccountService.On("FetchVouchers", string(publicKey)).Return(mockVouchersResponse, nil) + + mockSubPrefixDb.On("Put", ctx, []byte("sym"), []byte("1:SRF\n2:MILO")).Return(nil) + mockSubPrefixDb.On("Put", ctx, []byte("bal"), []byte("1:100\n2:200")).Return(nil) + mockSubPrefixDb.On("Put", ctx, []byte("deci"), []byte("1:6\n2:4")).Return(nil) + mockSubPrefixDb.On("Put", ctx, []byte("addr"), []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa")).Return(nil) + + _, err := h.CheckVouchers(ctx, "check_vouchers", []byte("")) + + assert.NoError(t, err) + + mockDataStore.AssertExpectations(t) + mockAccountService.AssertExpectations(t) +} + +func TestProcessVouchers(t *testing.T) { + holdings := []struct { + ContractAddress string `json:"contractAddress"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals string `json:"tokenDecimals"` + Balance string `json:"balance"` + }{ + {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, + {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, + } + + expectedResult := VoucherMetadata{ + Symbol: "1:SRF\n2:MILO", + Balance: "1:100\n2:200", + Decimal: "1:6\n2:4", + Address: "1:0xd4c288865Ce\n2:0x41c188d63Qa", + } + + result := processVouchers(holdings) + + assert.Equal(t, expectedResult, result) +} + +func TestGetVoucherList(t *testing.T) { + mockSubPrefixDb := new(mocks.MockSubPrefixDb) + + sessionId := "session123" + ctx := context.WithValue(context.Background(), "SessionId", sessionId) + + h := &Handlers{ + prefixDb: mockSubPrefixDb, + } + + mockSubPrefixDb.On("Get", ctx, []byte("sym")).Return([]byte("1:SRF\n2:MILO"), nil) + + res, err := h.GetVoucherList(ctx, "", []byte("")) + assert.NoError(t, err) + assert.Contains(t, res.Content, "1:SRF\n2:MILO") + + mockSubPrefixDb.AssertExpectations(t) +} + +func TestViewVoucher(t *testing.T) { + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Logf(err.Error()) + } + mockDataStore := new(mocks.MockUserDataStore) + mockSubPrefixDb := new(mocks.MockSubPrefixDb) + + sessionId := "session123" + ctx := context.WithValue(context.Background(), "SessionId", sessionId) + + h := &Handlers{ + userdataStore: mockDataStore, + flagManager: fm.parser, + prefixDb: mockSubPrefixDb, + } + + // Define mock voucher data + mockVoucherData := map[string]string{ + "sym": "1:SRF", + "bal": "1:100", + "deci": "1:6", + "addr": "1:0xd4c288865Ce", + } + + for key, value := range mockVoucherData { + mockSubPrefixDb.On("Get", ctx, []byte(key)).Return([]byte(value), nil) + } + + // Set up expectations for mockDataStore + expectedData := map[utils.DataTyp]string{ + utils.DATA_TEMPORARY_SYM: "SRF", + utils.DATA_TEMPORARY_BAL: "100", + utils.DATA_TEMPORARY_DECIMAL: "6", + utils.DATA_TEMPORARY_ADDRESS: "0xd4c288865Ce", + } + + for dataType, dataValue := range expectedData { + mockDataStore.On("WriteEntry", ctx, sessionId, dataType, []byte(dataValue)).Return(nil) + } + + res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1")) + assert.NoError(t, err) + assert.Contains(t, res.Content, "SRF\n100") + + mockDataStore.AssertExpectations(t) + mockSubPrefixDb.AssertExpectations(t) +} + +func TestGetVoucherData(t *testing.T) { + mockSubPrefixDb := new(mocks.MockSubPrefixDb) + ctx := context.Background() + + // Mocked voucher data + mockData := map[string][]byte{ + "sym": []byte("1:SRF\n2:MILO"), + "bal": []byte("1:100\n2:200"), + "deci": []byte("1:6\n2:4"), + "addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), + } + + // Mock Get calls + for key, value := range mockData { + mockSubPrefixDb.On("Get", ctx, []byte(key)).Return(value, nil) + } + + result, err := getVoucherData(ctx, mockSubPrefixDb, "1") + + assert.NoError(t, err) + assert.Equal(t, "SRF", result.Symbol) + assert.Equal(t, "100", result.Balance) + assert.Equal(t, "6", result.Decimal) + assert.Equal(t, "0xd4c288865Ce", result.Address) + + mockSubPrefixDb.AssertExpectations(t) +} + +func TestMatchVoucher(t *testing.T) { + symbols := "1:SRF\n2:MILO" + balances := "1:100\n2:200" + decimals := "1:6\n2:4" + addresses := "1:0xd4c288865Ce\n2:0x41c188d63Qa" + + // Test for valid voucher + symbol, balance, decimal, address := matchVoucher("2", symbols, balances, decimals, addresses) + + // Assertions for valid voucher + assert.Equal(t, "MILO", symbol) + assert.Equal(t, "200", balance) + assert.Equal(t, "4", decimal) + assert.Equal(t, "0x41c188d63Qa", address) + + // Test for non-existent voucher + symbol, balance, decimal, address = matchVoucher("3", symbols, balances, decimals, addresses) + + // Assertions for non-match + assert.Equal(t, "", symbol) + assert.Equal(t, "", balance) + assert.Equal(t, "", decimal) + assert.Equal(t, "", address) +} + +func TestStoreTemporaryVoucher(t *testing.T) { + mockDataStore := new(mocks.MockUserDataStore) + ctx := context.Background() + sessionId := "session123" + + voucherData := &VoucherMetadata{ + Symbol: "SRF", + Balance: "200", + Decimal: "6", + Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + } + + // Define expected entries to be written + expectedEntries := map[utils.DataTyp][]byte{ + utils.DATA_TEMPORARY_SYM: []byte("SRF"), + utils.DATA_TEMPORARY_BAL: []byte("200"), + utils.DATA_TEMPORARY_DECIMAL: []byte("6"), + utils.DATA_TEMPORARY_ADDRESS: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"), + } + + // Mock WriteEntry calls + for key, value := range expectedEntries { + mockDataStore.On("WriteEntry", ctx, sessionId, key, value).Return(nil) + } + + h := &Handlers{userdataStore: mockDataStore} + + err := h.storeTemporaryVoucher(ctx, sessionId, voucherData) + + assert.NoError(t, err) + mockDataStore.AssertExpectations(t) +} + +func TestSetVoucher(t *testing.T) { + mockDataStore := new(mocks.MockUserDataStore) + + sessionId := "session123" + ctx := context.WithValue(context.Background(), "SessionId", sessionId) + + // Define the temporary voucher data + tempData := &VoucherMetadata{ + Symbol: "SRF", + Balance: "200", + Decimal: "6", + Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + } + + // Define the expected active entries + activeEntries := map[utils.DataTyp][]byte{ + utils.DATA_ACTIVE_SYM: []byte(tempData.Symbol), + utils.DATA_ACTIVE_BAL: []byte(tempData.Balance), + utils.DATA_ACTIVE_DECIMAL: []byte(tempData.Decimal), + utils.DATA_ACTIVE_ADDRESS: []byte(tempData.Address), + } + + // Define the temporary entries to be cleared + tempEntries := map[utils.DataTyp][]byte{ + utils.DATA_TEMPORARY_SYM: []byte(""), + utils.DATA_TEMPORARY_BAL: []byte(""), + utils.DATA_TEMPORARY_DECIMAL: []byte(""), + utils.DATA_TEMPORARY_ADDRESS: []byte(""), + } + + // Mocking ReadEntry calls for temporary data retrieval + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM).Return([]byte(tempData.Symbol), nil) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL).Return([]byte(tempData.Balance), nil) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_DECIMAL).Return([]byte(tempData.Decimal), nil) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_ADDRESS).Return([]byte(tempData.Address), nil) + + // Mocking WriteEntry calls for setting active data + for key, value := range activeEntries { + mockDataStore.On("WriteEntry", ctx, sessionId, key, value).Return(nil) + } + + // Mocking WriteEntry calls for clearing temporary data + for key, value := range tempEntries { + mockDataStore.On("WriteEntry", ctx, sessionId, key, value).Return(nil) + } + + h := &Handlers{ + userdataStore: mockDataStore, + } + + res, err := h.SetVoucher(ctx, "someSym", []byte{}) + + assert.NoError(t, err) + + assert.Equal(t, string(tempData.Symbol), res.Content) + + mockDataStore.AssertExpectations(t) +} + +func TestGetTemporaryVoucherData(t *testing.T) { + mockDataStore := new(mocks.MockUserDataStore) + sessionId := "session123" + ctx := context.WithValue(context.Background(), "SessionId", sessionId) + + h := &Handlers{userdataStore: mockDataStore} + + // Mock temporary voucher data + tempData := &VoucherMetadata{ + Symbol: "SRF", + Balance: "100", + Decimal: "6", + Address: "0xd4c288865Ce", + } + + // Set up mocks for reading entries + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM).Return([]byte(tempData.Symbol), nil) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL).Return([]byte(tempData.Balance), nil) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_DECIMAL).Return([]byte(tempData.Decimal), nil) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_ADDRESS).Return([]byte(tempData.Address), nil) + + data, err := h.getTemporaryVoucherData(ctx, sessionId) + assert.NoError(t, err) + assert.Equal(t, tempData, data) + + mockDataStore.AssertExpectations(t) +} + +func TestUpdateVoucherData(t *testing.T) { + mockDataStore := new(mocks.MockUserDataStore) + ctx := context.Background() + sessionId := "session123" + + h := &Handlers{userdataStore: mockDataStore} + + data := &VoucherMetadata{ + Symbol: "SRF", + Balance: "100", + Decimal: "6", + Address: "0xd4c288865Ce", + } + + // Mock WriteEntry for active data + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(data.Symbol)).Return(nil) + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_BAL, []byte(data.Balance)).Return(nil) + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_DECIMAL, []byte(data.Decimal)).Return(nil) + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_ADDRESS, []byte(data.Address)).Return(nil) + + // Mock WriteEntry for clearing temporary data + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte("")).Return(nil) + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte("")).Return(nil) + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_DECIMAL, []byte("")).Return(nil) + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_ADDRESS, []byte("")).Return(nil) + + err := h.updateVoucherData(ctx, sessionId, data) + assert.NoError(t, err) + + mockDataStore.AssertExpectations(t) +} From 0480c02633067fa5638217327fb24b9d1fc62869 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 30 Oct 2024 00:52:23 +0300 Subject: [PATCH 08/15] Moved voucher related functions to the utils package --- internal/handlers/ussd/menuhandler.go | 179 +------------------------ internal/utils/vouchers.go | 184 ++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 172 deletions(-) create mode 100644 internal/utils/vouchers.go diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 98f6c9d..d4ed992 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1118,7 +1118,7 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) return res, nil } - data := processVouchers(vouchersResp.Result.Holdings) + data := utils.ProcessVouchers(vouchersResp.Result.Holdings) // Store all voucher data dataMap := map[string]string{ @@ -1137,31 +1137,6 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) return res, nil } -// processVouchers converts holdings into formatted strings -func processVouchers(holdings []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` -}) VoucherMetadata { - var data VoucherMetadata - var symbols, balances, decimals, addresses []string - - for i, h := range holdings { - symbols = append(symbols, fmt.Sprintf("%d:%s", i+1, h.TokenSymbol)) - balances = append(balances, fmt.Sprintf("%d:%s", i+1, h.Balance)) - decimals = append(decimals, fmt.Sprintf("%d:%s", i+1, h.TokenDecimals)) - addresses = append(addresses, fmt.Sprintf("%d:%s", i+1, h.ContractAddress)) - } - - data.Symbol = strings.Join(symbols, "\n") - data.Balance = strings.Join(balances, "\n") - data.Decimal = strings.Join(decimals, "\n") - data.Address = strings.Join(addresses, "\n") - - return data -} - // 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 @@ -1193,8 +1168,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r return res, nil } - // Retrieve the voucher metadata using the PrefixDb interface - metadata, err := getVoucherData(ctx, h.prefixDb, inputStr) + metadata, err := utils.GetVoucherData(ctx, h.prefixDb, inputStr) if err != nil { return res, fmt.Errorf("failed to retrieve voucher data: %v", err) } @@ -1204,7 +1178,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r return res, nil } - if err := h.storeTemporaryVoucher(ctx, sessionId, metadata); err != nil { + if err := utils.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil { return res, err } @@ -1214,86 +1188,9 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r return res, nil } -// getVoucherData retrieves and matches voucher data -func getVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*VoucherMetadata, error) { - keys := []string{"sym", "bal", "deci", "addr"} - data := make(map[string]string) - - for _, key := range keys { - value, err := db.Get(ctx, []byte(key)) - if err != nil { - return nil, fmt.Errorf("failed to get %s: %v", key, err) - } - data[key] = string(value) - } - - symbol, balance, decimal, address := matchVoucher(input, - data["sym"], - data["bal"], - data["deci"], - data["addr"]) - - if symbol == "" { - return nil, nil - } - - return &VoucherMetadata{ - Symbol: symbol, - Balance: balance, - Decimal: decimal, - Address: address, - }, nil -} - -// MatchVoucher finds the matching voucher symbol, balance, decimals and contract address based on the input. -func matchVoucher(input, symbols, balances, decimals, addresses string) (symbol, balance, decimal, address string) { - symList := strings.Split(symbols, "\n") - balList := strings.Split(balances, "\n") - decList := strings.Split(decimals, "\n") - addrList := strings.Split(addresses, "\n") - - for i, sym := range symList { - parts := strings.SplitN(sym, ":", 2) - if len(parts) != 2 { - continue - } - - if input == parts[0] || strings.EqualFold(input, parts[1]) { - symbol = parts[1] - if i < len(balList) { - balance = strings.SplitN(balList[i], ":", 2)[1] - } - if i < len(decList) { - decimal = strings.SplitN(decList[i], ":", 2)[1] - } - if i < len(addrList) { - address = strings.SplitN(addrList[i], ":", 2)[1] - } - break - } - } - return -} - -func (h *Handlers) storeTemporaryVoucher(ctx context.Context, sessionId string, data *VoucherMetadata) error { - entries := map[utils.DataTyp][]byte{ - utils.DATA_TEMPORARY_SYM: []byte(data.Symbol), - utils.DATA_TEMPORARY_BAL: []byte(data.Balance), - utils.DATA_TEMPORARY_DECIMAL: []byte(data.Decimal), - utils.DATA_TEMPORARY_ADDRESS: []byte(data.Address), - } - - for key, value := range entries { - if err := h.userdataStore.WriteEntry(ctx, sessionId, key, value); err != nil { - return err - } - } - return nil -} - +// 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 - var err error sessionId, ok := ctx.Value("SessionId").(string) if !ok { @@ -1301,78 +1198,16 @@ func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (re } // Get temporary data - tempData, err := h.getTemporaryVoucherData(ctx, sessionId) + tempData, err := utils.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId) if err != nil { return res, err } - // Set as active and clear temporary - if err := h.updateVoucherData(ctx, sessionId, tempData); err != nil { + // Set as active and clear temporary data + if err := utils.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil { return res, err } res.Content = tempData.Symbol return res, nil } - -func (h *Handlers) getTemporaryVoucherData(ctx context.Context, sessionId string) (*VoucherMetadata, error) { - store := h.userdataStore - - keys := []utils.DataTyp{ - utils.DATA_TEMPORARY_SYM, - utils.DATA_TEMPORARY_BAL, - utils.DATA_TEMPORARY_DECIMAL, - utils.DATA_TEMPORARY_ADDRESS, - } - - data := &VoucherMetadata{} - values := make([][]byte, len(keys)) - - for i, key := range keys { - value, err := store.ReadEntry(ctx, sessionId, key) - if err != nil { - return nil, err - } - values[i] = value - } - - data.Symbol = string(values[0]) - data.Balance = string(values[1]) - data.Decimal = string(values[2]) - data.Address = string(values[3]) - - return data, nil -} - -func (h *Handlers) updateVoucherData(ctx context.Context, sessionId string, data *VoucherMetadata) error { - // Set active voucher data - activeEntries := map[utils.DataTyp][]byte{ - utils.DATA_ACTIVE_SYM: []byte(data.Symbol), - utils.DATA_ACTIVE_BAL: []byte(data.Balance), - utils.DATA_ACTIVE_DECIMAL: []byte(data.Decimal), - utils.DATA_ACTIVE_ADDRESS: []byte(data.Address), - } - - // Clear temporary voucher data - tempEntries := map[utils.DataTyp][]byte{ - utils.DATA_TEMPORARY_SYM: []byte(""), - utils.DATA_TEMPORARY_BAL: []byte(""), - utils.DATA_TEMPORARY_DECIMAL: []byte(""), - utils.DATA_TEMPORARY_ADDRESS: []byte(""), - } - - // Write all entries - for key, value := range activeEntries { - if err := h.userdataStore.WriteEntry(ctx, sessionId, key, value); err != nil { - return err - } - } - - for key, value := range tempEntries { - if err := h.userdataStore.WriteEntry(ctx, sessionId, key, value); err != nil { - return err - } - } - - return nil -} diff --git a/internal/utils/vouchers.go b/internal/utils/vouchers.go new file mode 100644 index 0000000..11fd7d1 --- /dev/null +++ b/internal/utils/vouchers.go @@ -0,0 +1,184 @@ +package utils + +import ( + "fmt" + "strings" + "context" + + + "git.grassecon.net/urdt/ussd/internal/storage" +) + +// VoucherMetadata helps organize voucher data fields +type VoucherMetadata struct { + Symbol string + Balance string + Decimal string + Address string +} + +// ProcessVouchers converts holdings into formatted strings +func ProcessVouchers(holdings []struct { + ContractAddress string `json:"contractAddress"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals string `json:"tokenDecimals"` + Balance string `json:"balance"` +}) VoucherMetadata { + var data VoucherMetadata + var symbols, balances, decimals, addresses []string + + for i, h := range holdings { + symbols = append(symbols, fmt.Sprintf("%d:%s", i+1, h.TokenSymbol)) + balances = append(balances, fmt.Sprintf("%d:%s", i+1, h.Balance)) + decimals = append(decimals, fmt.Sprintf("%d:%s", i+1, h.TokenDecimals)) + addresses = append(addresses, fmt.Sprintf("%d:%s", i+1, h.ContractAddress)) + } + + data.Symbol = strings.Join(symbols, "\n") + data.Balance = strings.Join(balances, "\n") + data.Decimal = strings.Join(decimals, "\n") + data.Address = strings.Join(addresses, "\n") + + return data +} + +// GetVoucherData retrieves and matches voucher data +func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*VoucherMetadata, error) { + keys := []string{"sym", "bal", "deci", "addr"} + data := make(map[string]string) + + for _, key := range keys { + value, err := db.Get(ctx, []byte(key)) + if err != nil { + return nil, fmt.Errorf("failed to get %s: %v", key, err) + } + data[key] = string(value) + } + + symbol, balance, decimal, address := MatchVoucher(input, + data["sym"], + data["bal"], + data["deci"], + data["addr"]) + + if symbol == "" { + return nil, nil + } + + return &VoucherMetadata{ + Symbol: symbol, + Balance: balance, + Decimal: decimal, + Address: address, + }, nil +} + +// MatchVoucher finds the matching voucher symbol, balance, decimals and contract address based on the input. +func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol, balance, decimal, address string) { + symList := strings.Split(symbols, "\n") + balList := strings.Split(balances, "\n") + decList := strings.Split(decimals, "\n") + addrList := strings.Split(addresses, "\n") + + for i, sym := range symList { + parts := strings.SplitN(sym, ":", 2) + if len(parts) != 2 { + continue + } + + if input == parts[0] || strings.EqualFold(input, parts[1]) { + symbol = parts[1] + if i < len(balList) { + balance = strings.SplitN(balList[i], ":", 2)[1] + } + if i < len(decList) { + decimal = strings.SplitN(decList[i], ":", 2)[1] + } + if i < len(addrList) { + address = strings.SplitN(addrList[i], ":", 2)[1] + } + break + } + } + return +} + +// StoreTemporaryVoucher saves voucher metadata as temporary entries in the DataStore. +func StoreTemporaryVoucher(ctx context.Context, store DataStore, sessionId string, data *VoucherMetadata) error { + entries := map[DataTyp][]byte{ + DATA_TEMPORARY_SYM: []byte(data.Symbol), + DATA_TEMPORARY_BAL: []byte(data.Balance), + DATA_TEMPORARY_DECIMAL: []byte(data.Decimal), + DATA_TEMPORARY_ADDRESS: []byte(data.Address), + } + + for key, value := range entries { + if err := store.WriteEntry(ctx, sessionId, key, value); err != nil { + return err + } + } + return nil +} + +// GetTemporaryVoucherData retrieves temporary voucher metadata from the DataStore. +func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId string) (*VoucherMetadata, error) { + keys := []DataTyp{ + DATA_TEMPORARY_SYM, + DATA_TEMPORARY_BAL, + DATA_TEMPORARY_DECIMAL, + DATA_TEMPORARY_ADDRESS, + } + + data := &VoucherMetadata{} + values := make([][]byte, len(keys)) + + for i, key := range keys { + value, err := store.ReadEntry(ctx, sessionId, key) + if err != nil { + return nil, err + } + values[i] = value + } + + data.Symbol = string(values[0]) + data.Balance = string(values[1]) + data.Decimal = string(values[2]) + data.Address = string(values[3]) + + return data, nil +} + +// UpdateVoucherData sets the active voucher data and clears the temporary voucher data in the DataStore. +func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *VoucherMetadata) error { + // Active voucher data entries + activeEntries := map[DataTyp][]byte{ + DATA_ACTIVE_SYM: []byte(data.Symbol), + DATA_ACTIVE_BAL: []byte(data.Balance), + DATA_ACTIVE_DECIMAL: []byte(data.Decimal), + DATA_ACTIVE_ADDRESS: []byte(data.Address), + } + + // Clear temporary voucher data entries + tempEntries := map[DataTyp][]byte{ + DATA_TEMPORARY_SYM: []byte(""), + DATA_TEMPORARY_BAL: []byte(""), + DATA_TEMPORARY_DECIMAL: []byte(""), + DATA_TEMPORARY_ADDRESS: []byte(""), + } + + // Write active data + for key, value := range activeEntries { + if err := store.WriteEntry(ctx, sessionId, key, value); err != nil { + return err + } + } + + // Clear temporary data + for key, value := range tempEntries { + if err := store.WriteEntry(ctx, sessionId, key, value); err != nil { + return err + } + } + + return nil +} From 7241cdbfcbd92155a354c678f1044127c1a15aeb Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 30 Oct 2024 00:53:13 +0300 Subject: [PATCH 09/15] Updated the tests --- internal/handlers/ussd/menuhandler_test.go | 169 --------------- internal/utils/vouchers_test.go | 240 +++++++++++++++++++++ 2 files changed, 240 insertions(+), 169 deletions(-) create mode 100644 internal/utils/vouchers_test.go diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index c6ee8cf..6ea44a0 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -2050,29 +2050,6 @@ func TestCheckVouchers(t *testing.T) { mockAccountService.AssertExpectations(t) } -func TestProcessVouchers(t *testing.T) { - holdings := []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` - }{ - {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, - {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, - } - - expectedResult := VoucherMetadata{ - Symbol: "1:SRF\n2:MILO", - Balance: "1:100\n2:200", - Decimal: "1:6\n2:4", - Address: "1:0xd4c288865Ce\n2:0x41c188d63Qa", - } - - result := processVouchers(holdings) - - assert.Equal(t, expectedResult, result) -} - func TestGetVoucherList(t *testing.T) { mockSubPrefixDb := new(mocks.MockSubPrefixDb) @@ -2141,92 +2118,6 @@ func TestViewVoucher(t *testing.T) { mockSubPrefixDb.AssertExpectations(t) } -func TestGetVoucherData(t *testing.T) { - mockSubPrefixDb := new(mocks.MockSubPrefixDb) - ctx := context.Background() - - // Mocked voucher data - mockData := map[string][]byte{ - "sym": []byte("1:SRF\n2:MILO"), - "bal": []byte("1:100\n2:200"), - "deci": []byte("1:6\n2:4"), - "addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), - } - - // Mock Get calls - for key, value := range mockData { - mockSubPrefixDb.On("Get", ctx, []byte(key)).Return(value, nil) - } - - result, err := getVoucherData(ctx, mockSubPrefixDb, "1") - - assert.NoError(t, err) - assert.Equal(t, "SRF", result.Symbol) - assert.Equal(t, "100", result.Balance) - assert.Equal(t, "6", result.Decimal) - assert.Equal(t, "0xd4c288865Ce", result.Address) - - mockSubPrefixDb.AssertExpectations(t) -} - -func TestMatchVoucher(t *testing.T) { - symbols := "1:SRF\n2:MILO" - balances := "1:100\n2:200" - decimals := "1:6\n2:4" - addresses := "1:0xd4c288865Ce\n2:0x41c188d63Qa" - - // Test for valid voucher - symbol, balance, decimal, address := matchVoucher("2", symbols, balances, decimals, addresses) - - // Assertions for valid voucher - assert.Equal(t, "MILO", symbol) - assert.Equal(t, "200", balance) - assert.Equal(t, "4", decimal) - assert.Equal(t, "0x41c188d63Qa", address) - - // Test for non-existent voucher - symbol, balance, decimal, address = matchVoucher("3", symbols, balances, decimals, addresses) - - // Assertions for non-match - assert.Equal(t, "", symbol) - assert.Equal(t, "", balance) - assert.Equal(t, "", decimal) - assert.Equal(t, "", address) -} - -func TestStoreTemporaryVoucher(t *testing.T) { - mockDataStore := new(mocks.MockUserDataStore) - ctx := context.Background() - sessionId := "session123" - - voucherData := &VoucherMetadata{ - Symbol: "SRF", - Balance: "200", - Decimal: "6", - Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", - } - - // Define expected entries to be written - expectedEntries := map[utils.DataTyp][]byte{ - utils.DATA_TEMPORARY_SYM: []byte("SRF"), - utils.DATA_TEMPORARY_BAL: []byte("200"), - utils.DATA_TEMPORARY_DECIMAL: []byte("6"), - utils.DATA_TEMPORARY_ADDRESS: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"), - } - - // Mock WriteEntry calls - for key, value := range expectedEntries { - mockDataStore.On("WriteEntry", ctx, sessionId, key, value).Return(nil) - } - - h := &Handlers{userdataStore: mockDataStore} - - err := h.storeTemporaryVoucher(ctx, sessionId, voucherData) - - assert.NoError(t, err) - mockDataStore.AssertExpectations(t) -} - func TestSetVoucher(t *testing.T) { mockDataStore := new(mocks.MockUserDataStore) @@ -2285,63 +2176,3 @@ func TestSetVoucher(t *testing.T) { mockDataStore.AssertExpectations(t) } - -func TestGetTemporaryVoucherData(t *testing.T) { - mockDataStore := new(mocks.MockUserDataStore) - sessionId := "session123" - ctx := context.WithValue(context.Background(), "SessionId", sessionId) - - h := &Handlers{userdataStore: mockDataStore} - - // Mock temporary voucher data - tempData := &VoucherMetadata{ - Symbol: "SRF", - Balance: "100", - Decimal: "6", - Address: "0xd4c288865Ce", - } - - // Set up mocks for reading entries - mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM).Return([]byte(tempData.Symbol), nil) - mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL).Return([]byte(tempData.Balance), nil) - mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_DECIMAL).Return([]byte(tempData.Decimal), nil) - mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_ADDRESS).Return([]byte(tempData.Address), nil) - - data, err := h.getTemporaryVoucherData(ctx, sessionId) - assert.NoError(t, err) - assert.Equal(t, tempData, data) - - mockDataStore.AssertExpectations(t) -} - -func TestUpdateVoucherData(t *testing.T) { - mockDataStore := new(mocks.MockUserDataStore) - ctx := context.Background() - sessionId := "session123" - - h := &Handlers{userdataStore: mockDataStore} - - data := &VoucherMetadata{ - Symbol: "SRF", - Balance: "100", - Decimal: "6", - Address: "0xd4c288865Ce", - } - - // Mock WriteEntry for active data - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(data.Symbol)).Return(nil) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_BAL, []byte(data.Balance)).Return(nil) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_DECIMAL, []byte(data.Decimal)).Return(nil) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_ADDRESS, []byte(data.Address)).Return(nil) - - // Mock WriteEntry for clearing temporary data - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte("")).Return(nil) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte("")).Return(nil) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_DECIMAL, []byte("")).Return(nil) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_ADDRESS, []byte("")).Return(nil) - - err := h.updateVoucherData(ctx, sessionId, data) - assert.NoError(t, err) - - mockDataStore.AssertExpectations(t) -} diff --git a/internal/utils/vouchers_test.go b/internal/utils/vouchers_test.go new file mode 100644 index 0000000..75ae563 --- /dev/null +++ b/internal/utils/vouchers_test.go @@ -0,0 +1,240 @@ +package utils + +import ( + "context" + "testing" + + "git.grassecon.net/urdt/ussd/internal/storage" + "github.com/alecthomas/assert/v2" + "github.com/stretchr/testify/require" + + memdb "git.defalsify.org/vise.git/db/mem" +) + +// AssertEmptyValue checks if a value is empty/nil/zero +func AssertEmptyValue(t *testing.T, value []byte, msgAndArgs ...interface{}) { + assert.Equal(t, len(value), 0, msgAndArgs...) +} + +func TestMatchVoucher(t *testing.T) { + symbols := "1:SRF\n2:MILO" + balances := "1:100\n2:200" + decimals := "1:6\n2:4" + addresses := "1:0xd4c288865Ce\n2:0x41c188d63Qa" + + // Test for valid voucher + symbol, balance, decimal, address := MatchVoucher("2", symbols, balances, decimals, addresses) + + // Assertions for valid voucher + assert.Equal(t, "MILO", symbol) + assert.Equal(t, "200", balance) + assert.Equal(t, "4", decimal) + assert.Equal(t, "0x41c188d63Qa", address) + + // Test for non-existent voucher + symbol, balance, decimal, address = MatchVoucher("3", symbols, balances, decimals, addresses) + + // Assertions for non-match + assert.Equal(t, "", symbol) + assert.Equal(t, "", balance) + assert.Equal(t, "", decimal) + assert.Equal(t, "", address) +} + +func TestProcessVouchers(t *testing.T) { + holdings := []struct { + ContractAddress string `json:"contractAddress"` + TokenSymbol string `json:"tokenSymbol"` + TokenDecimals string `json:"tokenDecimals"` + Balance string `json:"balance"` + }{ + {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, + {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, + } + + expectedResult := VoucherMetadata{ + Symbol: "1:SRF\n2:MILO", + Balance: "1:100\n2:200", + Decimal: "1:6\n2:4", + Address: "1:0xd4c288865Ce\n2:0x41c188d63Qa", + } + + result := ProcessVouchers(holdings) + + assert.Equal(t, expectedResult, result) +} + +func TestGetVoucherData(t *testing.T) { + ctx := context.Background() + + db := memdb.NewMemDb() + err := db.Connect(ctx, "") + if err != nil { + t.Fatal(err) + } + spdb := storage.NewSubPrefixDb(db, []byte("vouchers")) + + // Test voucher data + mockData := map[string][]byte{ + "sym": []byte("1:SRF\n2:MILO"), + "bal": []byte("1:100\n2:200"), + "deci": []byte("1:6\n2:4"), + "addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), + } + + // Put the data + for key, value := range mockData { + err = spdb.Put(ctx, []byte(key), []byte(value)) + if err != nil { + t.Fatal(err) + } + } + + result, err := GetVoucherData(ctx, spdb, "1") + + assert.NoError(t, err) + assert.Equal(t, "SRF", result.Symbol) + assert.Equal(t, "100", result.Balance) + assert.Equal(t, "6", result.Decimal) + assert.Equal(t, "0xd4c288865Ce", result.Address) +} + +func TestStoreTemporaryVoucher(t *testing.T) { + ctx := context.Background() + sessionId := "session123" + + // Initialize memDb + db := memdb.NewMemDb() + err := db.Connect(ctx, "") + require.NoError(t, err) + defer db.Close() + + // Create UserDataStore with memDb + store := &UserDataStore{ + Db: db, + } + + // Test data + voucherData := &VoucherMetadata{ + Symbol: "SRF", + Balance: "200", + Decimal: "6", + Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + } + + // Execute the function being tested + err = StoreTemporaryVoucher(ctx, store, sessionId, voucherData) + assert.NoError(t, err) + + // Verify stored data + expectedEntries := map[DataTyp][]byte{ + DATA_TEMPORARY_SYM: []byte("SRF"), + DATA_TEMPORARY_BAL: []byte("200"), + DATA_TEMPORARY_DECIMAL: []byte("6"), + DATA_TEMPORARY_ADDRESS: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"), + } + + for key, expectedValue := range expectedEntries { + storedValue, err := store.ReadEntry(ctx, sessionId, key) + assert.NoError(t, err) + assert.Equal(t, expectedValue, storedValue, "Mismatch for key %v", key) + } +} + +func TestGetTemporaryVoucherData(t *testing.T) { + sessionId := "session123" + ctx := context.WithValue(context.Background(), "SessionId", sessionId) + + // Initialize memDb + db := memdb.NewMemDb() + err := db.Connect(ctx, "") + require.NoError(t, err) + defer db.Close() + + // Create UserDataStore with memDb + store := &UserDataStore{ + Db: db, + } + + // Test voucher data + tempData := &VoucherMetadata{ + Symbol: "SRF", + Balance: "200", + Decimal: "6", + Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + } + + // store the data + err = StoreTemporaryVoucher(ctx, store, sessionId, tempData) + assert.NoError(t, err) + + // Execute the function being tested + data, err := GetTemporaryVoucherData(ctx, store, sessionId) + assert.NoError(t, err) + assert.Equal(t, tempData, data) +} + +func TestUpdateVoucherData(t *testing.T) { + sessionId := "session123" + ctx := context.WithValue(context.Background(), "SessionId", sessionId) + + // Initialize memDb + db := memdb.NewMemDb() + err := db.Connect(ctx, "") + require.NoError(t, err) + defer db.Close() + + store := &UserDataStore{ + Db: db, + } + + // Test data + data := &VoucherMetadata{ + Symbol: "SRF", + Balance: "200", + Decimal: "6", + Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + } + + // First store some temporary data to verify it gets cleared + tempData := &VoucherMetadata{ + Symbol: "OLD", + Balance: "100", + Decimal: "8", + Address: "0xold", + } + err = StoreTemporaryVoucher(ctx, store, sessionId, tempData) + require.NoError(t, err) + + // Execute update + err = UpdateVoucherData(ctx, store, sessionId, data) + assert.NoError(t, err) + + // Verify active data was stored correctly + activeEntries := map[DataTyp][]byte{ + DATA_ACTIVE_SYM: []byte(data.Symbol), + DATA_ACTIVE_BAL: []byte(data.Balance), + DATA_ACTIVE_DECIMAL: []byte(data.Decimal), + DATA_ACTIVE_ADDRESS: []byte(data.Address), + } + + for key, expectedValue := range activeEntries { + storedValue, err := store.ReadEntry(ctx, sessionId, key) + assert.NoError(t, err) + assert.Equal(t, expectedValue, storedValue, "Active data mismatch for key %v", key) + } + + // Verify temporary data was cleared + tempKeys := []DataTyp{ + DATA_TEMPORARY_SYM, + DATA_TEMPORARY_BAL, + DATA_TEMPORARY_DECIMAL, + DATA_TEMPORARY_ADDRESS, + } + + for _, key := range tempKeys { + storedValue, err := store.ReadEntry(ctx, sessionId, key) + assert.NoError(t, err) + AssertEmptyValue(t, storedValue, "Temporary data not cleared for key %v", key) + } +} \ No newline at end of file From adaa0c63ef78486223ec5e9646640599ceda685d Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 30 Oct 2024 14:12:42 +0300 Subject: [PATCH 10/15] Extract common db operations for the test --- internal/utils/vouchers_test.go | 138 ++++++++++++++------------------ 1 file changed, 62 insertions(+), 76 deletions(-) diff --git a/internal/utils/vouchers_test.go b/internal/utils/vouchers_test.go index 75ae563..f26ee01 100644 --- a/internal/utils/vouchers_test.go +++ b/internal/utils/vouchers_test.go @@ -11,6 +11,25 @@ import ( memdb "git.defalsify.org/vise.git/db/mem" ) +// InitializeTestDb sets up and returns an in-memory database and store. +func InitializeTestDb(t *testing.T) (context.Context, *UserDataStore) { + ctx := context.Background() + + // Initialize memDb + db := memdb.NewMemDb() + err := db.Connect(ctx, "") + require.NoError(t, err, "Failed to connect to memDb") + + // Create UserDataStore with memDb + store := &UserDataStore{Db: db} + + t.Cleanup(func() { + db.Close() // Ensure the DB is closed after each test + }) + + return ctx, store +} + // AssertEmptyValue checks if a value is empty/nil/zero func AssertEmptyValue(t *testing.T, value []byte, msgAndArgs ...interface{}) { assert.Equal(t, len(value), 0, msgAndArgs...) @@ -100,31 +119,20 @@ func TestGetVoucherData(t *testing.T) { } func TestStoreTemporaryVoucher(t *testing.T) { - ctx := context.Background() + ctx, store := InitializeTestDb(t) sessionId := "session123" - - // Initialize memDb - db := memdb.NewMemDb() - err := db.Connect(ctx, "") - require.NoError(t, err) - defer db.Close() - - // Create UserDataStore with memDb - store := &UserDataStore{ - Db: db, - } // Test data voucherData := &VoucherMetadata{ - Symbol: "SRF", - Balance: "200", - Decimal: "6", - Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + Symbol: "SRF", + Balance: "200", + Decimal: "6", + Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", } // Execute the function being tested - err = StoreTemporaryVoucher(ctx, store, sessionId, voucherData) - assert.NoError(t, err) + err := StoreTemporaryVoucher(ctx, store, sessionId, voucherData) + require.NoError(t, err) // Verify stored data expectedEntries := map[DataTyp][]byte{ @@ -136,92 +144,70 @@ func TestStoreTemporaryVoucher(t *testing.T) { for key, expectedValue := range expectedEntries { storedValue, err := store.ReadEntry(ctx, sessionId, key) - assert.NoError(t, err) - assert.Equal(t, expectedValue, storedValue, "Mismatch for key %v", key) + require.NoError(t, err) + require.Equal(t, expectedValue, storedValue, "Mismatch for key %v", key) } } func TestGetTemporaryVoucherData(t *testing.T) { + ctx, store := InitializeTestDb(t) sessionId := "session123" - ctx := context.WithValue(context.Background(), "SessionId", sessionId) - - // Initialize memDb - db := memdb.NewMemDb() - err := db.Connect(ctx, "") - require.NoError(t, err) - defer db.Close() - - // Create UserDataStore with memDb - store := &UserDataStore{ - Db: db, - } // Test voucher data tempData := &VoucherMetadata{ - Symbol: "SRF", - Balance: "200", - Decimal: "6", - Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + Symbol: "SRF", + Balance: "200", + Decimal: "6", + Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", } - // store the data - err = StoreTemporaryVoucher(ctx, store, sessionId, tempData) - assert.NoError(t, err) - + // Store the data + err := StoreTemporaryVoucher(ctx, store, sessionId, tempData) + require.NoError(t, err) + // Execute the function being tested data, err := GetTemporaryVoucherData(ctx, store, sessionId) - assert.NoError(t, err) - assert.Equal(t, tempData, data) + require.NoError(t, err) + require.Equal(t, tempData, data) } func TestUpdateVoucherData(t *testing.T) { + ctx, store := InitializeTestDb(t) sessionId := "session123" - ctx := context.WithValue(context.Background(), "SessionId", sessionId) - // Initialize memDb - db := memdb.NewMemDb() - err := db.Connect(ctx, "") - require.NoError(t, err) - defer db.Close() - - store := &UserDataStore{ - Db: db, + // New voucher data + newData := &VoucherMetadata{ + Symbol: "SRF", + Balance: "200", + Decimal: "6", + Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", } - // Test data - data := &VoucherMetadata{ - Symbol: "SRF", - Balance: "200", - Decimal: "6", - Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", - } - - // First store some temporary data to verify it gets cleared + // Old temporary data tempData := &VoucherMetadata{ - Symbol: "OLD", - Balance: "100", - Decimal: "8", - Address: "0xold", + Symbol: "OLD", + Balance: "100", + Decimal: "8", + Address: "0xold", } - err = StoreTemporaryVoucher(ctx, store, sessionId, tempData) - require.NoError(t, err) + require.NoError(t, StoreTemporaryVoucher(ctx, store, sessionId, tempData)) // Execute update - err = UpdateVoucherData(ctx, store, sessionId, data) - assert.NoError(t, err) + err := UpdateVoucherData(ctx, store, sessionId, newData) + require.NoError(t, err) // Verify active data was stored correctly activeEntries := map[DataTyp][]byte{ - DATA_ACTIVE_SYM: []byte(data.Symbol), - DATA_ACTIVE_BAL: []byte(data.Balance), - DATA_ACTIVE_DECIMAL: []byte(data.Decimal), - DATA_ACTIVE_ADDRESS: []byte(data.Address), + DATA_ACTIVE_SYM: []byte(newData.Symbol), + DATA_ACTIVE_BAL: []byte(newData.Balance), + DATA_ACTIVE_DECIMAL: []byte(newData.Decimal), + DATA_ACTIVE_ADDRESS: []byte(newData.Address), } for key, expectedValue := range activeEntries { storedValue, err := store.ReadEntry(ctx, sessionId, key) - assert.NoError(t, err) - assert.Equal(t, expectedValue, storedValue, "Active data mismatch for key %v", key) + require.NoError(t, err) + require.Equal(t, expectedValue, storedValue, "Active data mismatch for key %v", key) } // Verify temporary data was cleared @@ -234,7 +220,7 @@ func TestUpdateVoucherData(t *testing.T) { for _, key := range tempKeys { storedValue, err := store.ReadEntry(ctx, sessionId, key) - assert.NoError(t, err) + require.NoError(t, err) AssertEmptyValue(t, storedValue, "Temporary data not cleared for key %v", key) } -} \ No newline at end of file +} From 66b34eaea4f83147e6f4e3296636eee0bf21e812 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 30 Oct 2024 18:21:49 +0300 Subject: [PATCH 11/15] get the ussd-data-service package --- go.mod | 2 ++ go.sum | 2 ++ 2 files changed, 4 insertions(+) diff --git a/go.mod b/go.mod index 0b30354..391c1a5 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,8 @@ require ( gopkg.in/leonelquinteros/gotext.v1 v1.3.1 ) +require github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a // indirect + require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect diff --git a/go.sum b/go.sum index d566e2c..0ba38c1 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1 github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU= github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo= +github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a h1:q/YH7nE2j8epNmFnTu0tU1vwtCxtQ6nH+d7hRVV5krU= +github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a/go.mod h1:hdKaKwqiW6/kphK4j/BhmuRlZDLo1+DYo3gYw5O0siw= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= From caafe495be8d60a04e36d4306d4c2509adce8a22 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 30 Oct 2024 18:30:55 +0300 Subject: [PATCH 12/15] use the dataserviceapi structs --- internal/handlers/ussd/menuhandler.go | 8 +-- internal/handlers/ussd/menuhandler_test.go | 43 +++---------- internal/models/vouchersresponse.go | 21 ++++--- .../testservice/TestAccountService.go | 17 +----- internal/utils/vouchers.go | 49 +++++++-------- internal/utils/vouchers_test.go | 60 +++++++++---------- 6 files changed, 74 insertions(+), 124 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index d4ed992..231da95 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1055,13 +1055,13 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by if db.IsNotFound(err) { publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) if err != nil { - return res, nil + return res, err } // Fetch vouchers from the API using the public key vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey)) if err != nil { - return res, nil + return res, err } // Return if there is no voucher @@ -1183,7 +1183,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r } res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) - res.Content = fmt.Sprintf("%s\n%s", metadata.Symbol, metadata.Balance) + res.Content = fmt.Sprintf("%s\n%s", metadata.TokenSymbol, metadata.Balance) return res, nil } @@ -1208,6 +1208,6 @@ func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (re return res, err } - res.Content = tempData.Symbol + res.Content = tempData.TokenSymbol return res, nil } diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 6ea44a0..2b76bf1 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -24,6 +24,8 @@ import ( "github.com/grassrootseconomics/eth-custodial/pkg/api" testdataloader "github.com/peteole/testdata-loader" "github.com/stretchr/testify/require" + + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) var ( @@ -1898,12 +1900,10 @@ func TestSetDefaultVoucher(t *testing.T) { if err != nil { t.Logf(err.Error()) } - flag_no_active_voucher, err := fm.GetFlag("flag_no_active_voucher") if err != nil { t.Logf(err.Error()) } - // Define session ID and mock data sessionId := "session123" publicKey := "0X13242618721" @@ -1920,20 +1920,8 @@ func TestSetDefaultVoucher(t *testing.T) { vouchersResp: &models.VoucherHoldingResponse{ Ok: true, Description: "Vouchers fetched successfully", - Result: struct { - Holdings []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` - } `json:"holdings"` - }{ - Holdings: []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` - }{ + Result: models.VoucherResult{ + Holdings: []dataserviceapi.TokenHoldings{ { ContractAddress: "0x123", TokenSymbol: "TOKEN1", @@ -1950,20 +1938,8 @@ func TestSetDefaultVoucher(t *testing.T) { vouchersResp: &models.VoucherHoldingResponse{ Ok: true, Description: "No vouchers available", - Result: struct { - Holdings []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` - } `json:"holdings"` - }{ - Holdings: []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` - }{}, + Result: models.VoucherResult{ + Holdings: []dataserviceapi.TokenHoldings{}, }, }, expectedResult: resource.Result{ @@ -2025,12 +2001,7 @@ func TestCheckVouchers(t *testing.T) { mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil) mockVouchersResponse := &models.VoucherHoldingResponse{} - mockVouchersResponse.Result.Holdings = []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` - }{ + mockVouchersResponse.Result.Holdings = []dataserviceapi.TokenHoldings{ {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, } diff --git a/internal/models/vouchersresponse.go b/internal/models/vouchersresponse.go index 010730f..09b085d 100644 --- a/internal/models/vouchersresponse.go +++ b/internal/models/vouchersresponse.go @@ -1,15 +1,14 @@ package models -// VoucherHoldingResponse represents a single voucher holding +import dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" + type VoucherHoldingResponse struct { - Ok bool `json:"ok"` - Description string `json:"description"` - Result struct { - Holdings []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` - } `json:"holdings"` - } `json:"result"` + Ok bool `json:"ok"` + Description string `json:"description"` + Result VoucherResult `json:"result"` +} + +// VoucherResult holds the list of token holdings +type VoucherResult struct { + Holdings []dataserviceapi.TokenHoldings `json:"holdings"` } diff --git a/internal/testutil/testservice/TestAccountService.go b/internal/testutil/testservice/TestAccountService.go index 6332345..745b80d 100644 --- a/internal/testutil/testservice/TestAccountService.go +++ b/internal/testutil/testservice/TestAccountService.go @@ -7,6 +7,7 @@ import ( "git.grassecon.net/urdt/ussd/internal/models" "github.com/grassrootseconomics/eth-custodial/pkg/api" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) type TestAccountService struct { @@ -75,20 +76,8 @@ func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) { return &models.VoucherHoldingResponse{ Ok: true, - Result: struct { - Holdings []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` - } `json:"holdings"` - }{ - Holdings: []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` - }{ + Result: models.VoucherResult{ + Holdings: []dataserviceapi.TokenHoldings{ { ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee", TokenSymbol: "SRF", diff --git a/internal/utils/vouchers.go b/internal/utils/vouchers.go index 11fd7d1..2aed42a 100644 --- a/internal/utils/vouchers.go +++ b/internal/utils/vouchers.go @@ -1,12 +1,12 @@ package utils import ( + "context" "fmt" "strings" - "context" - "git.grassecon.net/urdt/ussd/internal/storage" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) // VoucherMetadata helps organize voucher data fields @@ -18,12 +18,7 @@ type VoucherMetadata struct { } // ProcessVouchers converts holdings into formatted strings -func ProcessVouchers(holdings []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` -}) VoucherMetadata { +func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata { var data VoucherMetadata var symbols, balances, decimals, addresses []string @@ -43,7 +38,7 @@ func ProcessVouchers(holdings []struct { } // GetVoucherData retrieves and matches voucher data -func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*VoucherMetadata, error) { +func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) { keys := []string{"sym", "bal", "deci", "addr"} data := make(map[string]string) @@ -65,11 +60,11 @@ func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*Vo return nil, nil } - return &VoucherMetadata{ - Symbol: symbol, - Balance: balance, - Decimal: decimal, - Address: address, + return &dataserviceapi.TokenHoldings{ + TokenSymbol: string(symbol), + Balance: string(balance), + TokenDecimals: string(decimal), + ContractAddress: string(address), }, nil } @@ -104,12 +99,12 @@ func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol, } // StoreTemporaryVoucher saves voucher metadata as temporary entries in the DataStore. -func StoreTemporaryVoucher(ctx context.Context, store DataStore, sessionId string, data *VoucherMetadata) error { +func StoreTemporaryVoucher(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error { entries := map[DataTyp][]byte{ - DATA_TEMPORARY_SYM: []byte(data.Symbol), + DATA_TEMPORARY_SYM: []byte(data.TokenSymbol), DATA_TEMPORARY_BAL: []byte(data.Balance), - DATA_TEMPORARY_DECIMAL: []byte(data.Decimal), - DATA_TEMPORARY_ADDRESS: []byte(data.Address), + DATA_TEMPORARY_DECIMAL: []byte(data.TokenDecimals), + DATA_TEMPORARY_ADDRESS: []byte(data.ContractAddress), } for key, value := range entries { @@ -121,7 +116,7 @@ func StoreTemporaryVoucher(ctx context.Context, store DataStore, sessionId strin } // GetTemporaryVoucherData retrieves temporary voucher metadata from the DataStore. -func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId string) (*VoucherMetadata, error) { +func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId string) (*dataserviceapi.TokenHoldings, error) { keys := []DataTyp{ DATA_TEMPORARY_SYM, DATA_TEMPORARY_BAL, @@ -129,7 +124,7 @@ func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId str DATA_TEMPORARY_ADDRESS, } - data := &VoucherMetadata{} + data := &dataserviceapi.TokenHoldings{} values := make([][]byte, len(keys)) for i, key := range keys { @@ -140,22 +135,22 @@ func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId str values[i] = value } - data.Symbol = string(values[0]) + data.TokenSymbol = string(values[0]) data.Balance = string(values[1]) - data.Decimal = string(values[2]) - data.Address = string(values[3]) + data.TokenDecimals = string(values[2]) + data.ContractAddress = string(values[3]) return data, nil } // UpdateVoucherData sets the active voucher data and clears the temporary voucher data in the DataStore. -func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *VoucherMetadata) error { +func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error { // Active voucher data entries activeEntries := map[DataTyp][]byte{ - DATA_ACTIVE_SYM: []byte(data.Symbol), + DATA_ACTIVE_SYM: []byte(data.TokenSymbol), DATA_ACTIVE_BAL: []byte(data.Balance), - DATA_ACTIVE_DECIMAL: []byte(data.Decimal), - DATA_ACTIVE_ADDRESS: []byte(data.Address), + DATA_ACTIVE_DECIMAL: []byte(data.TokenDecimals), + DATA_ACTIVE_ADDRESS: []byte(data.ContractAddress), } // Clear temporary voucher data entries diff --git a/internal/utils/vouchers_test.go b/internal/utils/vouchers_test.go index f26ee01..8f8f18e 100644 --- a/internal/utils/vouchers_test.go +++ b/internal/utils/vouchers_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" memdb "git.defalsify.org/vise.git/db/mem" + dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) // InitializeTestDb sets up and returns an in-memory database and store. @@ -61,12 +62,7 @@ func TestMatchVoucher(t *testing.T) { } func TestProcessVouchers(t *testing.T) { - holdings := []struct { - ContractAddress string `json:"contractAddress"` - TokenSymbol string `json:"tokenSymbol"` - TokenDecimals string `json:"tokenDecimals"` - Balance string `json:"balance"` - }{ + holdings := []dataserviceapi.TokenHoldings{ {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, } @@ -112,10 +108,10 @@ func TestGetVoucherData(t *testing.T) { result, err := GetVoucherData(ctx, spdb, "1") assert.NoError(t, err) - assert.Equal(t, "SRF", result.Symbol) + assert.Equal(t, "SRF", result.TokenSymbol) assert.Equal(t, "100", result.Balance) - assert.Equal(t, "6", result.Decimal) - assert.Equal(t, "0xd4c288865Ce", result.Address) + assert.Equal(t, "6", result.TokenDecimals) + assert.Equal(t, "0xd4c288865Ce", result.ContractAddress) } func TestStoreTemporaryVoucher(t *testing.T) { @@ -123,11 +119,11 @@ func TestStoreTemporaryVoucher(t *testing.T) { sessionId := "session123" // Test data - voucherData := &VoucherMetadata{ - Symbol: "SRF", - Balance: "200", - Decimal: "6", - Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + voucherData := &dataserviceapi.TokenHoldings{ + TokenSymbol: "SRF", + Balance: "200", + TokenDecimals: "6", + ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", } // Execute the function being tested @@ -154,11 +150,11 @@ func TestGetTemporaryVoucherData(t *testing.T) { sessionId := "session123" // Test voucher data - tempData := &VoucherMetadata{ - Symbol: "SRF", - Balance: "200", - Decimal: "6", - Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + tempData := &dataserviceapi.TokenHoldings{ + TokenSymbol: "SRF", + Balance: "200", + TokenDecimals: "6", + ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", } // Store the data @@ -176,19 +172,19 @@ func TestUpdateVoucherData(t *testing.T) { sessionId := "session123" // New voucher data - newData := &VoucherMetadata{ - Symbol: "SRF", - Balance: "200", - Decimal: "6", - Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + newData := &dataserviceapi.TokenHoldings{ + TokenSymbol: "SRF", + Balance: "200", + TokenDecimals: "6", + ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", } // Old temporary data - tempData := &VoucherMetadata{ - Symbol: "OLD", - Balance: "100", - Decimal: "8", - Address: "0xold", + tempData := &dataserviceapi.TokenHoldings{ + TokenSymbol: "OLD", + Balance: "100", + TokenDecimals: "8", + ContractAddress: "0xold", } require.NoError(t, StoreTemporaryVoucher(ctx, store, sessionId, tempData)) @@ -198,10 +194,10 @@ func TestUpdateVoucherData(t *testing.T) { // Verify active data was stored correctly activeEntries := map[DataTyp][]byte{ - DATA_ACTIVE_SYM: []byte(newData.Symbol), + DATA_ACTIVE_SYM: []byte(newData.TokenSymbol), DATA_ACTIVE_BAL: []byte(newData.Balance), - DATA_ACTIVE_DECIMAL: []byte(newData.Decimal), - DATA_ACTIVE_ADDRESS: []byte(newData.Address), + DATA_ACTIVE_DECIMAL: []byte(newData.TokenDecimals), + DATA_ACTIVE_ADDRESS: []byte(newData.ContractAddress), } for key, expectedValue := range activeEntries { From 8b399781e893ab6b678b0d38b7b960f0b60a9a4a Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 30 Oct 2024 18:40:03 +0300 Subject: [PATCH 13/15] make the VoucherMetadata more descriptive --- internal/handlers/ussd/menuhandler.go | 16 ++++----------- internal/handlers/ussd/menuhandler_test.go | 24 +++++++++++----------- internal/utils/vouchers.go | 18 ++++++++-------- internal/utils/vouchers_test.go | 8 ++++---- 4 files changed, 29 insertions(+), 37 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 231da95..3de4590 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -58,14 +58,6 @@ func (fm *FlagManager) GetFlag(label string) (uint32, error) { return fm.parser.GetFlag(label) } -// VoucherMetadata helps organize voucher data fields -type VoucherMetadata struct { - Symbol string - Balance string - Decimal string - Address string -} - type Handlers struct { pe *persist.Persister st *state.State @@ -1122,10 +1114,10 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) // Store all voucher data dataMap := map[string]string{ - "sym": data.Symbol, - "bal": data.Balance, - "deci": data.Decimal, - "addr": data.Address, + "sym": data.Symbols, + "bal": data.Balances, + "deci": data.Decimals, + "addr": data.Addresses, } for key, value := range dataMap { diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 2b76bf1..12f155e 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -2096,19 +2096,19 @@ func TestSetVoucher(t *testing.T) { ctx := context.WithValue(context.Background(), "SessionId", sessionId) // Define the temporary voucher data - tempData := &VoucherMetadata{ - Symbol: "SRF", - Balance: "200", - Decimal: "6", - Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", + tempData := &dataserviceapi.TokenHoldings{ + TokenSymbol: "SRF", + Balance: "200", + TokenDecimals: "6", + ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9", } // Define the expected active entries activeEntries := map[utils.DataTyp][]byte{ - utils.DATA_ACTIVE_SYM: []byte(tempData.Symbol), + utils.DATA_ACTIVE_SYM: []byte(tempData.TokenSymbol), utils.DATA_ACTIVE_BAL: []byte(tempData.Balance), - utils.DATA_ACTIVE_DECIMAL: []byte(tempData.Decimal), - utils.DATA_ACTIVE_ADDRESS: []byte(tempData.Address), + utils.DATA_ACTIVE_DECIMAL: []byte(tempData.TokenDecimals), + utils.DATA_ACTIVE_ADDRESS: []byte(tempData.ContractAddress), } // Define the temporary entries to be cleared @@ -2120,10 +2120,10 @@ func TestSetVoucher(t *testing.T) { } // Mocking ReadEntry calls for temporary data retrieval - mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM).Return([]byte(tempData.Symbol), nil) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM).Return([]byte(tempData.TokenSymbol), nil) mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL).Return([]byte(tempData.Balance), nil) - mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_DECIMAL).Return([]byte(tempData.Decimal), nil) - mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_ADDRESS).Return([]byte(tempData.Address), nil) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_DECIMAL).Return([]byte(tempData.TokenDecimals), nil) + mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_ADDRESS).Return([]byte(tempData.ContractAddress), nil) // Mocking WriteEntry calls for setting active data for key, value := range activeEntries { @@ -2143,7 +2143,7 @@ func TestSetVoucher(t *testing.T) { assert.NoError(t, err) - assert.Equal(t, string(tempData.Symbol), res.Content) + assert.Equal(t, string(tempData.TokenSymbol), res.Content) mockDataStore.AssertExpectations(t) } diff --git a/internal/utils/vouchers.go b/internal/utils/vouchers.go index 2aed42a..73a95a6 100644 --- a/internal/utils/vouchers.go +++ b/internal/utils/vouchers.go @@ -9,12 +9,12 @@ import ( dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) -// VoucherMetadata helps organize voucher data fields +// VoucherMetadata helps organize data fields type VoucherMetadata struct { - Symbol string - Balance string - Decimal string - Address string + Symbols string + Balances string + Decimals string + Addresses string } // ProcessVouchers converts holdings into formatted strings @@ -29,10 +29,10 @@ func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata { addresses = append(addresses, fmt.Sprintf("%d:%s", i+1, h.ContractAddress)) } - data.Symbol = strings.Join(symbols, "\n") - data.Balance = strings.Join(balances, "\n") - data.Decimal = strings.Join(decimals, "\n") - data.Address = strings.Join(addresses, "\n") + data.Symbols = strings.Join(symbols, "\n") + data.Balances = strings.Join(balances, "\n") + data.Decimals = strings.Join(decimals, "\n") + data.Addresses = strings.Join(addresses, "\n") return data } diff --git a/internal/utils/vouchers_test.go b/internal/utils/vouchers_test.go index 8f8f18e..953182e 100644 --- a/internal/utils/vouchers_test.go +++ b/internal/utils/vouchers_test.go @@ -68,10 +68,10 @@ func TestProcessVouchers(t *testing.T) { } expectedResult := VoucherMetadata{ - Symbol: "1:SRF\n2:MILO", - Balance: "1:100\n2:200", - Decimal: "1:6\n2:4", - Address: "1:0xd4c288865Ce\n2:0x41c188d63Qa", + Symbols: "1:SRF\n2:MILO", + Balances: "1:100\n2:200", + Decimals: "1:6\n2:4", + Addresses: "1:0xd4c288865Ce\n2:0x41c188d63Qa", } result := ProcessVouchers(holdings) From b8bbd88078356e007809b0576200e7dad942d462 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 31 Oct 2024 14:26:28 +0300 Subject: [PATCH 14/15] retain the temporary data for it to be overwritten --- internal/utils/vouchers.go | 17 +---------------- internal/utils/vouchers_test.go | 19 ------------------- 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/internal/utils/vouchers.go b/internal/utils/vouchers.go index 73a95a6..dc6cc4a 100644 --- a/internal/utils/vouchers.go +++ b/internal/utils/vouchers.go @@ -143,7 +143,7 @@ func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId str return data, nil } -// UpdateVoucherData sets the active voucher data and clears the temporary voucher data in the DataStore. +// UpdateVoucherData sets the active voucher data in the DataStore. func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error { // Active voucher data entries activeEntries := map[DataTyp][]byte{ @@ -153,14 +153,6 @@ func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, d DATA_ACTIVE_ADDRESS: []byte(data.ContractAddress), } - // Clear temporary voucher data entries - tempEntries := map[DataTyp][]byte{ - DATA_TEMPORARY_SYM: []byte(""), - DATA_TEMPORARY_BAL: []byte(""), - DATA_TEMPORARY_DECIMAL: []byte(""), - DATA_TEMPORARY_ADDRESS: []byte(""), - } - // Write active data for key, value := range activeEntries { if err := store.WriteEntry(ctx, sessionId, key, value); err != nil { @@ -168,12 +160,5 @@ func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, d } } - // Clear temporary data - for key, value := range tempEntries { - if err := store.WriteEntry(ctx, sessionId, key, value); err != nil { - return err - } - } - return nil } diff --git a/internal/utils/vouchers_test.go b/internal/utils/vouchers_test.go index 953182e..a609d27 100644 --- a/internal/utils/vouchers_test.go +++ b/internal/utils/vouchers_test.go @@ -31,11 +31,6 @@ func InitializeTestDb(t *testing.T) (context.Context, *UserDataStore) { return ctx, store } -// AssertEmptyValue checks if a value is empty/nil/zero -func AssertEmptyValue(t *testing.T, value []byte, msgAndArgs ...interface{}) { - assert.Equal(t, len(value), 0, msgAndArgs...) -} - func TestMatchVoucher(t *testing.T) { symbols := "1:SRF\n2:MILO" balances := "1:100\n2:200" @@ -205,18 +200,4 @@ func TestUpdateVoucherData(t *testing.T) { require.NoError(t, err) require.Equal(t, expectedValue, storedValue, "Active data mismatch for key %v", key) } - - // Verify temporary data was cleared - tempKeys := []DataTyp{ - DATA_TEMPORARY_SYM, - DATA_TEMPORARY_BAL, - DATA_TEMPORARY_DECIMAL, - DATA_TEMPORARY_ADDRESS, - } - - for _, key := range tempKeys { - storedValue, err := store.ReadEntry(ctx, sessionId, key) - require.NoError(t, err) - AssertEmptyValue(t, storedValue, "Temporary data not cleared for key %v", key) - } } From e6a369dcddea338d04717babc1503fde10181368 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 31 Oct 2024 14:44:42 +0300 Subject: [PATCH 15/15] remove unused code --- internal/utils/vouchers.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/utils/vouchers.go b/internal/utils/vouchers.go index dc6cc4a..b027cc1 100644 --- a/internal/utils/vouchers.go +++ b/internal/utils/vouchers.go @@ -77,10 +77,7 @@ func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol, for i, sym := range symList { parts := strings.SplitN(sym, ":", 2) - if len(parts) != 2 { - continue - } - + if input == parts[0] || strings.EqualFold(input, parts[1]) { symbol = parts[1] if i < len(balList) {