voucher-data #138
2
go.mod
@ -12,6 +12,8 @@ require (
|
|||||||
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
|
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a // indirect
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
2
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/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 h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU=
|
||||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
|
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 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
|
||||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
|
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=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
@ -66,6 +66,7 @@ type Handlers struct {
|
|||||||
userdataStore utils.DataStore
|
userdataStore utils.DataStore
|
||||||
flagManager *asm.FlagParser
|
flagManager *asm.FlagParser
|
||||||
accountService server.AccountServiceInterface
|
accountService server.AccountServiceInterface
|
||||||
|
prefixDb storage.PrefixDb
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService server.AccountServiceInterface) (*Handlers, error) {
|
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService server.AccountServiceInterface) (*Handlers, error) {
|
||||||
@ -75,10 +76,14 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService s
|
|||||||
userDb := &utils.UserDataStore{
|
userDb := &utils.UserDataStore{
|
||||||
Db: userdataStore,
|
Db: userdataStore,
|
||||||
}
|
}
|
||||||
|
// Instantiate the SubPrefixDb with "vouchers" prefix
|
||||||
|
prefixDb := storage.NewSubPrefixDb(userdataStore, []byte("vouchers"))
|
||||||
|
|
||||||
h := &Handlers{
|
h := &Handlers{
|
||||||
userdataStore: userDb,
|
userdataStore: userDb,
|
||||||
flagManager: appFlags,
|
flagManager: appFlags,
|
||||||
accountService: accountService,
|
accountService: accountService,
|
||||||
|
prefixDb: prefixDb,
|
||||||
}
|
}
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
@ -1051,13 +1056,13 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by
|
|||||||
if db.IsNotFound(err) {
|
if db.IsNotFound(err) {
|
||||||
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, nil
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch vouchers from the API using the public key
|
// Fetch vouchers from the API using the public key
|
||||||
vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
|
vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, nil
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return if there is no voucher
|
// Return if there is no voucher
|
||||||
@ -1114,54 +1119,33 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// process voucher data
|
data := utils.ProcessVouchers(vouchersResp.Result.Holdings)
|
||||||
voucherSymbolList, voucherBalanceList := ProcessVouchers(vouchersResp.Result.Holdings)
|
|
||||||
|
|
||||||
prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers"))
|
// Store all voucher data
|
||||||
err = prefixdb.Put(ctx, []byte("sym"), []byte(voucherSymbolList))
|
dataMap := map[string]string{
|
||||||
if err != nil {
|
"sym": data.Symbols,
|
||||||
return res, nil
|
"bal": data.Balances,
|
||||||
|
"deci": data.Decimals,
|
||||||
|
"addr": data.Addresses,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = prefixdb.Put(ctx, []byte("bal"), []byte(voucherBalanceList))
|
for key, value := range dataMap {
|
||||||
if err != nil {
|
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
|
||||||
return res, nil
|
return res, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessVouchers formats the holdings into symbol and balance lists.
|
|
||||||
func ProcessVouchers(holdings []struct {
|
|
||||||
ContractAddress string `json:"contractAddress"`
|
|
||||||
TokenSymbol string `json:"tokenSymbol"`
|
|
||||||
TokenDecimals string `json:"tokenDecimals"`
|
|
||||||
Balance string `json:"balance"`
|
|
||||||
}) (string, string) {
|
|
||||||
var numberedSymbols, numberedBalances []string
|
|
||||||
|
|
||||||
for i, voucher := range holdings {
|
|
||||||
numberedSymbols = append(numberedSymbols, fmt.Sprintf("%d:%s", i+1, voucher.TokenSymbol))
|
|
||||||
numberedBalances = append(numberedBalances, fmt.Sprintf("%d:%s", i+1, voucher.Balance))
|
|
||||||
}
|
|
||||||
|
|
||||||
voucherSymbolList := strings.Join(numberedSymbols, "\n")
|
|
||||||
voucherBalanceList := strings.Join(numberedBalances, "\n")
|
|
||||||
|
|
||||||
return voucherSymbolList, voucherBalanceList
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVoucherList fetches the list of vouchers and formats them
|
// GetVoucherList fetches the list of vouchers and formats them
|
||||||
func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
Alfred-mk marked this conversation as resolved
Outdated
|
|||||||
var res resource.Result
|
var res resource.Result
|
||||||
|
|
||||||
// Read vouchers from the store
|
// Read vouchers from the store
|
||||||
store := h.userdataStore
|
voucherData, err := h.prefixDb.Get(ctx, []byte("sym"))
|
||||||
prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers"))
|
|
||||||
|
|
||||||
voucherData, err := prefixdb.Get(ctx, []byte("sym"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, nil
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Content = string(voucherData)
|
res.Content = string(voucherData)
|
||||||
@ -1172,8 +1156,6 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte)
|
|||||||
// ViewVoucher retrieves the token holding and balance from the subprefixDB
|
// ViewVoucher retrieves the token holding and balance from the subprefixDB
|
||||||
func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
store := h.userdataStore
|
|
||||||
|
|
||||||
sessionId, ok := ctx.Value("SessionId").(string)
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
@ -1182,126 +1164,51 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
|
|||||||
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
||||||
|
|
||||||
inputStr := string(input)
|
inputStr := string(input)
|
||||||
|
|
||||||
if inputStr == "0" || inputStr == "99" {
|
if inputStr == "0" || inputStr == "99" {
|
||||||
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers"))
|
metadata, err := utils.GetVoucherData(ctx, h.prefixDb, inputStr)
|
||||||
|
|
||||||
// Retrieve the voucher symbol list
|
|
||||||
voucherSymbolList, err := prefixdb.Get(ctx, []byte("sym"))
|
|
||||||
if err != nil {
|
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
|
if metadata == nil {
|
||||||
voucherBalanceList, err := prefixdb.Get(ctx, []byte("bal"))
|
|
||||||
if err != nil {
|
|
||||||
return res, fmt.Errorf("failed to retrieve voucher balance list: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// match the voucher symbol and balance with the input
|
|
||||||
matchedSymbol, matchedBalance := MatchVoucher(inputStr, string(voucherSymbolList), string(voucherBalanceList))
|
|
||||||
|
|
||||||
// If a match is found, write the temporary sym and balance
|
|
||||||
if matchedSymbol != "" && matchedBalance != "" {
|
|
||||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte(matchedSymbol))
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte(matchedBalance))
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
|
||||||
res.Content = fmt.Sprintf("%s\n%s", matchedSymbol, matchedBalance)
|
|
||||||
} else {
|
|
||||||
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := utils.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||||
|
res.Content = fmt.Sprintf("%s\n%s", metadata.TokenSymbol, metadata.Balance)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchVoucher finds the matching voucher symbol and balance based on the input.
|
// SetVoucher retrieves the temp voucher data and sets it as the active data
|
||||||
func MatchVoucher(inputStr string, voucherSymbols, voucherBalances string) (string, string) {
|
|
||||||
// Split the lists into slices for processing
|
|
||||||
symbols := strings.Split(voucherSymbols, "\n")
|
|
||||||
balances := strings.Split(voucherBalances, "\n")
|
|
||||||
|
|
||||||
var matchedSymbol, matchedBalance string
|
|
||||||
|
|
||||||
for i, symbol := range symbols {
|
|
||||||
symbolParts := strings.SplitN(symbol, ":", 2)
|
|
||||||
if len(symbolParts) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
voucherNum := symbolParts[0]
|
|
||||||
voucherSymbol := symbolParts[1]
|
|
||||||
|
|
||||||
// Check if input matches either the number or the symbol
|
|
||||||
if inputStr == voucherNum || strings.EqualFold(inputStr, voucherSymbol) {
|
|
||||||
matchedSymbol = voucherSymbol
|
|
||||||
// Ensure there's a corresponding balance
|
|
||||||
if i < len(balances) {
|
|
||||||
matchedBalance = strings.SplitN(balances[i], ":", 2)[1]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchedSymbol, matchedBalance
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetVoucher retrieves the temporary sym and balance,
|
|
||||||
// sets them as the active data and
|
|
||||||
// clears the temporary data
|
|
||||||
func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
var err error
|
|
||||||
store := h.userdataStore
|
|
||||||
|
|
||||||
sessionId, ok := ctx.Value("SessionId").(string)
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the current temporary symbol
|
// Get temporary data
|
||||||
temporarySym, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_SYM)
|
tempData, err := utils.GetTemporaryVoucherData(ctx, h.userdataStore, 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 {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the active symbol
|
// Set as active and clear temporary data
|
||||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(temporarySym))
|
if err := utils.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil {
|
||||||
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
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset the temporary symbol
|
res.Content = tempData.TokenSymbol
|
||||||
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 res, nil
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ import (
|
|||||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||||
testdataloader "github.com/peteole/testdata-loader"
|
testdataloader "github.com/peteole/testdata-loader"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -1489,8 +1491,8 @@ func TestValidateAmount(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Test with invalid amount format",
|
name: "Test with invalid amount format",
|
||||||
input: []byte("0.02ms"),
|
input: []byte("0.02ms"),
|
||||||
activeBal: []byte("5"),
|
activeBal: []byte("5"),
|
||||||
expectedResult: resource.Result{
|
expectedResult: resource.Result{
|
||||||
FlagSet: []uint32{flag_invalid_amount},
|
FlagSet: []uint32{flag_invalid_amount},
|
||||||
@ -1819,40 +1821,6 @@ func TestConfirmPin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetVoucher(t *testing.T) {
|
|
||||||
mockDataStore := new(mocks.MockUserDataStore)
|
|
||||||
|
|
||||||
sessionId := "session123"
|
|
||||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
|
||||||
|
|
||||||
temporarySym := []byte("tempSym")
|
|
||||||
temporaryBal := []byte("tempBal")
|
|
||||||
|
|
||||||
// Set expectations for the mock data store
|
|
||||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM).Return(temporarySym, nil)
|
|
||||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL).Return(temporaryBal, nil)
|
|
||||||
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_SYM, temporarySym).Return(nil)
|
|
||||||
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACTIVE_BAL, temporaryBal).Return(nil)
|
|
||||||
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte("")).Return(nil)
|
|
||||||
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte("")).Return(nil)
|
|
||||||
|
|
||||||
h := &Handlers{
|
|
||||||
userdataStore: mockDataStore,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the method under test
|
|
||||||
res, err := h.SetVoucher(ctx, "someSym", []byte{})
|
|
||||||
|
|
||||||
// Assert that no errors occurred
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Assert that the result content is correct
|
|
||||||
assert.Equal(t, string(temporarySym), res.Content)
|
|
||||||
|
|
||||||
// Assert that expectations were met
|
|
||||||
mockDataStore.AssertExpectations(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetchCustodialBalances(t *testing.T) {
|
func TestFetchCustodialBalances(t *testing.T) {
|
||||||
fm, err := NewFlagManager(flagsPath)
|
fm, err := NewFlagManager(flagsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1934,3 +1902,256 @@ 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: models.VoucherResult{
|
||||||
|
Holdings: []dataserviceapi.TokenHoldings{
|
||||||
|
{
|
||||||
|
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: models.VoucherResult{
|
||||||
|
Holdings: []dataserviceapi.TokenHoldings{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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 = []dataserviceapi.TokenHoldings{
|
||||||
|
{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 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 TestSetVoucher(t *testing.T) {
|
||||||
|
mockDataStore := new(mocks.MockUserDataStore)
|
||||||
|
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||||
|
|
||||||
|
// Define the temporary voucher data
|
||||||
|
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.TokenSymbol),
|
||||||
|
utils.DATA_ACTIVE_BAL: []byte(tempData.Balance),
|
||||||
|
utils.DATA_ACTIVE_DECIMAL: []byte(tempData.TokenDecimals),
|
||||||
|
utils.DATA_ACTIVE_ADDRESS: []byte(tempData.ContractAddress),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.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.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 {
|
||||||
|
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.TokenSymbol), res.Content)
|
||||||
|
|
||||||
|
mockDataStore.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
// VoucherHoldingResponse represents a single voucher holding
|
import dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
|
||||||
type VoucherHoldingResponse struct {
|
type VoucherHoldingResponse struct {
|
||||||
Ok bool `json:"ok"`
|
Ok bool `json:"ok"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Result struct {
|
Result VoucherResult `json:"result"`
|
||||||
Holdings []struct {
|
}
|
||||||
ContractAddress string `json:"contractAddress"`
|
|
||||||
TokenSymbol string `json:"tokenSymbol"`
|
// VoucherResult holds the list of token holdings
|
||||||
TokenDecimals string `json:"tokenDecimals"`
|
type VoucherResult struct {
|
||||||
Balance string `json:"balance"`
|
Holdings []dataserviceapi.TokenHoldings `json:"holdings"`
|
||||||
} `json:"holdings"`
|
|
||||||
} `json:"result"`
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,14 @@ const (
|
|||||||
DATATYPE_USERSUB = 64
|
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 {
|
type SubPrefixDb struct {
|
||||||
store db.Db
|
store db.Db
|
||||||
pfx []byte
|
pfx []byte
|
||||||
@ -29,11 +37,7 @@ func (s *SubPrefixDb) toKey(k []byte) []byte {
|
|||||||
func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
|
func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
|
||||||
s.store.SetPrefix(DATATYPE_USERSUB)
|
s.store.SetPrefix(DATATYPE_USERSUB)
|
||||||
key = s.toKey(key)
|
key = s.toKey(key)
|
||||||
v, err := s.store.Get(ctx, key)
|
return s.store.Get(ctx, key)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
|
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
|
||||||
|
21
internal/testutil/mocks/subprefixdbmock.go
Normal file
@ -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)
|
||||||
|
}
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"git.grassecon.net/urdt/ussd/internal/models"
|
"git.grassecon.net/urdt/ussd/internal/models"
|
||||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestAccountService struct {
|
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) {
|
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
|
||||||
return &models.VoucherHoldingResponse{
|
return &models.VoucherHoldingResponse{
|
||||||
Ok: true,
|
Ok: true,
|
||||||
Result: struct {
|
Result: models.VoucherResult{
|
||||||
Holdings []struct {
|
Holdings: []dataserviceapi.TokenHoldings{
|
||||||
ContractAddress string `json:"contractAddress"`
|
|
||||||
TokenSymbol string `json:"tokenSymbol"`
|
|
||||||
TokenDecimals string `json:"tokenDecimals"`
|
|
||||||
Balance string `json:"balance"`
|
|
||||||
} `json:"holdings"`
|
|
||||||
}{
|
|
||||||
Holdings: []struct {
|
|
||||||
ContractAddress string `json:"contractAddress"`
|
|
||||||
TokenSymbol string `json:"tokenSymbol"`
|
|
||||||
TokenDecimals string `json:"tokenDecimals"`
|
|
||||||
Balance string `json:"balance"`
|
|
||||||
}{
|
|
||||||
{
|
{
|
||||||
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
|
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
|
||||||
TokenSymbol: "SRF",
|
TokenSymbol: "SRF",
|
||||||
|
@ -29,6 +29,10 @@ const (
|
|||||||
DATA_TEMPORARY_BAL
|
DATA_TEMPORARY_BAL
|
||||||
DATA_ACTIVE_BAL
|
DATA_ACTIVE_BAL
|
||||||
DATA_PUBLIC_KEY_REVERSE
|
DATA_PUBLIC_KEY_REVERSE
|
||||||
|
DATA_TEMPORARY_DECIMAL
|
||||||
|
DATA_ACTIVE_DECIMAL
|
||||||
|
DATA_TEMPORARY_ADDRESS
|
||||||
|
DATA_ACTIVE_ADDRESS
|
||||||
)
|
)
|
||||||
|
|
||||||
func typToBytes(typ DataTyp) []byte {
|
func typToBytes(typ DataTyp) []byte {
|
||||||
|
161
internal/utils/vouchers.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.grassecon.net/urdt/ussd/internal/storage"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VoucherMetadata helps organize data fields
|
||||||
|
type VoucherMetadata struct {
|
||||||
|
Symbols string
|
||||||
|
Balances string
|
||||||
|
Decimals string
|
||||||
|
Addresses string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessVouchers converts holdings into formatted strings
|
||||||
|
func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) 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.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVoucherData retrieves and matches voucher data
|
||||||
|
func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, 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 &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: string(symbol),
|
||||||
|
Balance: string(balance),
|
||||||
|
TokenDecimals: string(decimal),
|
||||||
|
ContractAddress: string(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)
|
||||||
|
|
||||||
Alfred-mk marked this conversation as resolved
Outdated
lash
commented
When will this condition occur? When will this condition occur?
Alfred-mk
commented
It will only occur if the string is not well formatted in the "1:SRF" format, such as it being "1SRF" or "1 SRF" Ideally, this will not be encountered but I added this as an edge case in the unlikely event that it does It will only occur if the string is not well formatted in the "1:SRF" format, such as it being "1SRF" or "1 SRF"
Ideally, this will not be encountered but I added this as an edge case in the unlikely event that it does
lash
commented
but shouldnt that raise an error, as it should never happen? but shouldnt that raise an error, as it should never happen?
Alfred-mk
commented
On further assessment, I see it best to remove the code as the condition would never occur. The data being matched comes from the db, and for this condition to occur means an issue lies with the functions that process and store the data On further assessment, I see it best to remove the code as the condition would never occur.
The data being matched comes from the db, and for this condition to occur means an issue lies with the functions that process and store the data
|
|||||||
|
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 *dataserviceapi.TokenHoldings) error {
|
||||||
|
entries := map[DataTyp][]byte{
|
||||||
|
DATA_TEMPORARY_SYM: []byte(data.TokenSymbol),
|
||||||
|
DATA_TEMPORARY_BAL: []byte(data.Balance),
|
||||||
|
DATA_TEMPORARY_DECIMAL: []byte(data.TokenDecimals),
|
||||||
|
DATA_TEMPORARY_ADDRESS: []byte(data.ContractAddress),
|
||||||
|
}
|
||||||
|
|
||||||
|
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) (*dataserviceapi.TokenHoldings, error) {
|
||||||
|
keys := []DataTyp{
|
||||||
|
DATA_TEMPORARY_SYM,
|
||||||
|
DATA_TEMPORARY_BAL,
|
||||||
|
DATA_TEMPORARY_DECIMAL,
|
||||||
|
DATA_TEMPORARY_ADDRESS,
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &dataserviceapi.TokenHoldings{}
|
||||||
|
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.TokenSymbol = string(values[0])
|
||||||
|
data.Balance = string(values[1])
|
||||||
|
data.TokenDecimals = string(values[2])
|
||||||
|
data.ContractAddress = string(values[3])
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{
|
||||||
|
DATA_ACTIVE_SYM: []byte(data.TokenSymbol),
|
||||||
|
DATA_ACTIVE_BAL: []byte(data.Balance),
|
||||||
|
DATA_ACTIVE_DECIMAL: []byte(data.TokenDecimals),
|
||||||
|
DATA_ACTIVE_ADDRESS: []byte(data.ContractAddress),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write active data
|
||||||
|
for key, value := range activeEntries {
|
||||||
|
if err := store.WriteEntry(ctx, sessionId, key, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
Alfred-mk marked this conversation as resolved
lash
commented
Do we need to clear them here? Can't we just overwrite later? Do we need to clear them here? Can't we just overwrite later?
Alfred-mk
commented
They can be left and overwritten with any new temporary data I wanted to work on this functionality on a different PR, where we'll make use of a single temporary data row They can be left and overwritten with any new temporary data
I wanted to work on this functionality on a different PR, where we'll make use of a single temporary data row
lash
commented
ok just drop the lines then? ok just drop the lines then?
|
|||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
203
internal/utils/vouchers_test.go
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
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"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := []dataserviceapi.TokenHoldings{
|
||||||
|
{ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"},
|
||||||
|
{ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedResult := VoucherMetadata{
|
||||||
|
Symbols: "1:SRF\n2:MILO",
|
||||||
|
Balances: "1:100\n2:200",
|
||||||
|
Decimals: "1:6\n2:4",
|
||||||
|
Addresses: "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.TokenSymbol)
|
||||||
|
assert.Equal(t, "100", result.Balance)
|
||||||
|
assert.Equal(t, "6", result.TokenDecimals)
|
||||||
|
assert.Equal(t, "0xd4c288865Ce", result.ContractAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreTemporaryVoucher(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestDb(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
|
||||||
|
// Test data
|
||||||
|
voucherData := &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: "SRF",
|
||||||
|
Balance: "200",
|
||||||
|
TokenDecimals: "6",
|
||||||
|
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the function being tested
|
||||||
|
err := StoreTemporaryVoucher(ctx, store, sessionId, voucherData)
|
||||||
|
require.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)
|
||||||
|
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"
|
||||||
|
|
||||||
|
// Test voucher data
|
||||||
|
tempData := &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: "SRF",
|
||||||
|
Balance: "200",
|
||||||
|
TokenDecimals: "6",
|
||||||
|
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the data
|
||||||
|
err := StoreTemporaryVoucher(ctx, store, sessionId, tempData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Execute the function being tested
|
||||||
|
data, err := GetTemporaryVoucherData(ctx, store, sessionId)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tempData, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateVoucherData(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestDb(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
|
||||||
|
// New voucher data
|
||||||
|
newData := &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: "SRF",
|
||||||
|
Balance: "200",
|
||||||
|
TokenDecimals: "6",
|
||||||
|
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old temporary data
|
||||||
|
tempData := &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: "OLD",
|
||||||
|
Balance: "100",
|
||||||
|
TokenDecimals: "8",
|
||||||
|
ContractAddress: "0xold",
|
||||||
|
}
|
||||||
|
require.NoError(t, StoreTemporaryVoucher(ctx, store, sessionId, tempData))
|
||||||
|
|
||||||
|
// Execute update
|
||||||
|
err := UpdateVoucherData(ctx, store, sessionId, newData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify active data was stored correctly
|
||||||
|
activeEntries := map[DataTyp][]byte{
|
||||||
|
DATA_ACTIVE_SYM: []byte(newData.TokenSymbol),
|
||||||
|
DATA_ACTIVE_BAL: []byte(newData.Balance),
|
||||||
|
DATA_ACTIVE_DECIMAL: []byte(newData.TokenDecimals),
|
||||||
|
DATA_ACTIVE_ADDRESS: []byte(newData.ContractAddress),
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, expectedValue := range activeEntries {
|
||||||
|
storedValue, err := store.ReadEntry(ctx, sessionId, key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedValue, storedValue, "Active data mismatch for key %v", key)
|
||||||
|
}
|
||||||
|
}
|
this is now the same struct as
VoucherMetadata
can we consolidate please?