From 3ef64271e7429d00012c579155a20c97784ec38d Mon Sep 17 00:00:00 2001
From: alfred-mk <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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 <alfredmwaik@gmail.com>
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) {