forked from grassrootseconomics/visedriver
		
	Merge pull request 'voucher-data' (#138) from voucher-data into master
Reviewed-on: urdt/ussd#138
This commit is contained in:
		
						commit
						074345fcf9
					
				
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								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 | ||||
|  | ||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										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/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= | ||||
|  | ||||
| @ -66,6 +66,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) { | ||||
| @ -75,10 +76,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 | ||||
| } | ||||
| @ -1051,13 +1056,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
 | ||||
| @ -1114,54 +1119,33 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) | ||||
| 		return res, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// process voucher data
 | ||||
| 	voucherSymbolList, voucherBalanceList := ProcessVouchers(vouchersResp.Result.Holdings) | ||||
| 	data := utils.ProcessVouchers(vouchersResp.Result.Holdings) | ||||
| 
 | ||||
| 	prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers")) | ||||
| 	err = prefixdb.Put(ctx, []byte("sym"), []byte(voucherSymbolList)) | ||||
| 	if err != nil { | ||||
| 		return res, nil | ||||
| 	// Store all voucher data
 | ||||
| 	dataMap := map[string]string{ | ||||
| 		"sym":  data.Symbols, | ||||
| 		"bal":  data.Balances, | ||||
| 		"deci": data.Decimals, | ||||
| 		"addr": data.Addresses, | ||||
| 	} | ||||
| 
 | ||||
| 	err = prefixdb.Put(ctx, []byte("bal"), []byte(voucherBalanceList)) | ||||
| 	if err != nil { | ||||
| 		return res, nil | ||||
| 	for key, value := range dataMap { | ||||
| 		if err := h.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 { | ||||
| 	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
 | ||||
| func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) { | ||||
| 	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) | ||||
| @ -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
 | ||||
| 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") | ||||
| @ -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") | ||||
| 
 | ||||
| 	inputStr := string(input) | ||||
| 
 | ||||
| 	if inputStr == "0" || inputStr == "99" { | ||||
| 		res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) | ||||
| 		return res, nil | ||||
| 	} | ||||
| 
 | ||||
| 	prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers")) | ||||
| 
 | ||||
| 	// Retrieve the voucher symbol list
 | ||||
| 	voucherSymbolList, err := prefixdb.Get(ctx, []byte("sym")) | ||||
| 	metadata, err := utils.GetVoucherData(ctx, h.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 := 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 | ||||
| } | ||||
| 
 | ||||
| // 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") | ||||
| 
 | ||||
| 	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
 | ||||
| // 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 | ||||
| 	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) | ||||
| 	if err != nil { | ||||
| 		return res, err | ||||
| 	} | ||||
| 	// get the current temporary balance
 | ||||
| 	temporaryBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_BAL) | ||||
| 	// Get temporary data
 | ||||
| 	tempData, err := utils.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId) | ||||
| 	if err != nil { | ||||
| 		return res, 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 { | ||||
| 	// Set as active and clear temporary data
 | ||||
| 	if err := utils.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil { | ||||
| 		return res, 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) | ||||
| 
 | ||||
| 	res.Content = tempData.TokenSymbol | ||||
| 	return res, nil | ||||
| } | ||||
|  | ||||
| @ -25,6 +25,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 ( | ||||
| @ -1489,8 +1491,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}, | ||||
| @ -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) { | ||||
| 	fm, err := NewFlagManager(flagsPath) | ||||
| 	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 | ||||
| 
 | ||||
| // 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"` | ||||
| } | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
							
								
								
									
										21
									
								
								internal/testutil/mocks/subprefixdbmock.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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" | ||||
| 	"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", | ||||
|  | ||||
| @ -29,6 +29,10 @@ const ( | ||||
| 	DATA_TEMPORARY_BAL | ||||
| 	DATA_ACTIVE_BAL | ||||
| 	DATA_PUBLIC_KEY_REVERSE | ||||
| 	DATA_TEMPORARY_DECIMAL | ||||
| 	DATA_ACTIVE_DECIMAL | ||||
| 	DATA_TEMPORARY_ADDRESS | ||||
| 	DATA_ACTIVE_ADDRESS | ||||
| ) | ||||
| 
 | ||||
| func typToBytes(typ DataTyp) []byte { | ||||
|  | ||||
							
								
								
									
										161
									
								
								internal/utils/vouchers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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) | ||||
| 	 | ||||
| 		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 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										203
									
								
								internal/utils/vouchers_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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) | ||||
| 	} | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user