Compare commits
59 Commits
address-se
...
consolidat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7ea8fa651
|
||
|
|
e75aa2c1f7 | ||
|
|
63eed81d3d
|
||
|
|
f4ca4454ea
|
||
| 5f666382ab | |||
|
|
ce917d9e89
|
||
|
|
3ce25d0e14
|
||
|
|
0b4bf58107
|
||
| 074345fcf9 | |||
|
|
e6a369dcdd
|
||
|
|
b8bbd88078
|
||
|
|
d25128287e
|
||
|
|
c45fcda2f1
|
||
|
|
211cc1f775
|
||
|
|
981f7ca4f6
|
||
|
|
05ed236e03
|
||
|
|
a31cac4e50
|
||
|
|
8b399781e8
|
||
|
|
caafe495be
|
||
|
|
66b34eaea4
|
||
|
|
adaa0c63ef
|
||
|
|
843b0d1e7e | ||
|
|
cb997159f7
|
||
|
|
7241cdbfcb
|
||
|
|
0480c02633
|
||
|
|
03c32fd265 | ||
| 727f54ee57 | |||
|
|
2c361e5b96
|
||
|
|
131f3bcf46
|
||
|
|
520f5abdcd
|
||
|
|
5d294b663c
|
||
|
|
dc782d87a8
|
||
|
|
ddae746b9d
|
||
|
|
4e1b2d5ddb
|
||
|
|
d8800a665d
|
||
|
|
6c3ff0e9db | ||
|
|
3ef64271e7
|
||
|
|
3792bfdc1f
|
||
|
|
a0e97cfe5b
|
||
|
|
a3be98c514
|
||
|
|
2b34b3a75c
|
||
|
|
f99486c190
|
||
|
|
57a07af8ca
|
||
|
|
5692440099
|
||
|
|
651969668f
|
||
|
|
f3a028f1fc
|
||
|
|
1e6cf6a33a
|
||
|
|
2725323f16
|
||
|
|
306666a15e
|
||
|
|
e05dbb4885
|
||
|
|
eaac771722
|
||
|
|
f3a5178de7
|
||
|
|
549d09890b
|
||
|
|
4a9ef6b5f2
|
||
|
|
961d8d1a5c
|
||
|
|
a904cdbf44
|
||
|
|
3af943f77c
|
||
|
|
14455f65cb
|
||
|
|
0c08654df3
|
18
common/hex.go
Normal file
18
common/hex.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func NormalizeHex(s string) (string, error) {
|
||||
if len(s) >= 2 {
|
||||
if s[:2] == "0x" {
|
||||
s = s[2:]
|
||||
}
|
||||
}
|
||||
r, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(r), nil
|
||||
}
|
||||
9
go.mod
9
go.mod
@@ -7,19 +7,19 @@ toolchain go1.23.2
|
||||
require (
|
||||
git.defalsify.org/vise.git v0.2.1-0.20241017112704-307fa6fcdc6b
|
||||
github.com/alecthomas/assert/v2 v2.2.2
|
||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta
|
||||
github.com/peteole/testdata-loader v0.3.0
|
||||
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
|
||||
|
||||
)
|
||||
|
||||
require github.com/joho/godotenv v1.5.1
|
||||
require github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a // indirect
|
||||
|
||||
require (
|
||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
@@ -42,7 +42,4 @@ require (
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // 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=
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/config"
|
||||
"git.grassecon.net/urdt/ussd/internal/models"
|
||||
@@ -31,9 +30,6 @@ type AccountServiceInterface interface {
|
||||
type AccountService struct {
|
||||
}
|
||||
|
||||
type TestAccountService struct {
|
||||
}
|
||||
|
||||
// Parameters:
|
||||
// - trackingId: A unique identifier for the account.This should be obtained from a previous call to
|
||||
// CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the
|
||||
@@ -187,88 +183,3 @@ func (as *AccountService) FetchVouchers(ctx context.Context, publicKey string) (
|
||||
}
|
||||
return &holdings, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
||||
return &api.OKResponse{
|
||||
Ok: true,
|
||||
Description: "Account creation request received successfully",
|
||||
Result: map[string]any{"publicKey": "0x48ADca309b5085852207FAaf2816eD72B52F527C", "trackingId": "28ebe84d-b925-472c-87ae-bbdfa1fb97be"},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
|
||||
balanceResponse := &models.BalanceResponse{
|
||||
Ok: true,
|
||||
Result: struct {
|
||||
Balance string `json:"balance"`
|
||||
Nonce json.Number `json:"nonce"`
|
||||
}{
|
||||
Balance: "0.003 CELO",
|
||||
Nonce: json.Number("0"),
|
||||
},
|
||||
}
|
||||
return balanceResponse, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
|
||||
return &api.OKResponse{
|
||||
Ok: true,
|
||||
Description: "Account creation succeeded",
|
||||
Result: map[string]any{
|
||||
"active": true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
|
||||
trackResponse := &models.TrackStatusResponse{
|
||||
Ok: true,
|
||||
Result: struct {
|
||||
Transaction struct {
|
||||
CreatedAt time.Time "json:\"createdAt\""
|
||||
Status string "json:\"status\""
|
||||
TransferValue json.Number "json:\"transferValue\""
|
||||
TxHash string "json:\"txHash\""
|
||||
TxType string "json:\"txType\""
|
||||
}
|
||||
}{
|
||||
Transaction: models.Transaction{
|
||||
CreatedAt: time.Now(),
|
||||
Status: "SUCCESS",
|
||||
TransferValue: json.Number("0.5"),
|
||||
TxHash: "0x123abc456def",
|
||||
TxType: "transfer",
|
||||
},
|
||||
},
|
||||
}
|
||||
return trackResponse, nil
|
||||
}
|
||||
|
||||
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"`
|
||||
}{
|
||||
{
|
||||
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
|
||||
TokenSymbol: "SRF",
|
||||
TokenDecimals: "6",
|
||||
Balance: "2745987",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"git.defalsify.org/vise.git/persist"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.defalsify.org/vise.git/state"
|
||||
"git.grassecon.net/urdt/ussd/common"
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers/server"
|
||||
"git.grassecon.net/urdt/ussd/internal/utils"
|
||||
"gopkg.in/leonelquinteros/gotext.v1"
|
||||
@@ -64,6 +65,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) {
|
||||
@@ -73,10 +75,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
|
||||
}
|
||||
@@ -152,15 +158,22 @@ func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, r
|
||||
data := map[utils.DataTyp]string{
|
||||
utils.DATA_TRACKING_ID: trackingId,
|
||||
utils.DATA_PUBLIC_KEY: publicKey,
|
||||
utils.DATA_ADDRESS: sessionId,
|
||||
}
|
||||
store := h.userdataStore
|
||||
for key, value := range data {
|
||||
store := h.userdataStore
|
||||
err := store.WriteEntry(ctx, sessionId, key, []byte(value))
|
||||
err = store.WriteEntry(ctx, sessionId, key, []byte(value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
publicKeyNormalized, err := common.NormalizeHex(publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = store.WriteEntry(ctx, publicKeyNormalized, utils.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_account_created)
|
||||
return nil
|
||||
|
||||
@@ -208,7 +221,7 @@ func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_PIN
|
||||
// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_VALUE
|
||||
// during the account creation process
|
||||
// and during the change PIN process
|
||||
func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
@@ -233,7 +246,7 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
|
||||
|
||||
store := h.userdataStore
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN, []byte(accountPIN))
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(accountPIN))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
@@ -250,7 +263,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt
|
||||
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
|
||||
|
||||
store := h.userdataStore
|
||||
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN)
|
||||
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
@@ -281,7 +294,7 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
store := h.userdataStore
|
||||
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN)
|
||||
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
@@ -320,10 +333,18 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
if len(input) > 0 {
|
||||
firstName := string(input)
|
||||
store := h.userdataStore
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_FIRST_NAME, []byte(firstName))
|
||||
firstName := string(input)
|
||||
store := h.userdataStore
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
if allowUpdate {
|
||||
temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE)
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_FIRST_NAME, []byte(temporaryFirstName))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(firstName))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
@@ -340,18 +361,24 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
store := h.userdataStore
|
||||
familyName := string(input)
|
||||
|
||||
if len(input) > 0 {
|
||||
familyName := string(input)
|
||||
store := h.userdataStore
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_FAMILY_NAME, []byte(familyName))
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE)
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_FAMILY_NAME, []byte(temporaryFamilyName))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
return res, fmt.Errorf("a family name cannot be less than one character")
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(familyName))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -363,10 +390,19 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
if len(input) == 4 {
|
||||
yob := string(input)
|
||||
store := h.userdataStore
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_YOB, []byte(yob))
|
||||
yob := string(input)
|
||||
store := h.userdataStore
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryYob, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE)
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_YOB, []byte(temporaryYob))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(yob))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
@@ -383,11 +419,20 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
location := string(input)
|
||||
store := h.userdataStore
|
||||
|
||||
if len(input) > 0 {
|
||||
location := string(input)
|
||||
store := h.userdataStore
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_LOCATION, []byte(location))
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryLocation, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE)
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_LOCATION, []byte(temporaryLocation))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(location))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
@@ -407,9 +452,20 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
|
||||
}
|
||||
gender := strings.Split(symbol, "_")[1]
|
||||
store := h.userdataStore
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(gender))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryGender, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE)
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(temporaryGender))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(gender))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
@@ -423,12 +479,22 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
if len(input) > 0 {
|
||||
offerings := string(input)
|
||||
store := h.userdataStore
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_OFFERINGS, []byte(offerings))
|
||||
offerings := string(input)
|
||||
store := h.userdataStore
|
||||
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE)
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_OFFERINGS, []byte(temporaryOfferings))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(offerings))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,7 +599,6 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
okResponse, err = h.accountService.TrackAccountStatus(ctx, string(publicKey))
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
@@ -1033,13 +1098,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
|
||||
@@ -1096,54 +1161,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)
|
||||
@@ -1154,8 +1198,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")
|
||||
@@ -1164,126 +1206,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
|
||||
}
|
||||
|
||||
@@ -15,14 +15,18 @@ import (
|
||||
"git.defalsify.org/vise.git/persist"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.defalsify.org/vise.git/state"
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers/server"
|
||||
"git.grassecon.net/urdt/ussd/internal/mocks"
|
||||
"git.grassecon.net/urdt/ussd/internal/models"
|
||||
"git.grassecon.net/urdt/ussd/internal/testutil/mocks"
|
||||
"git.grassecon.net/urdt/ussd/internal/testutil/testservice"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/common"
|
||||
"git.grassecon.net/urdt/ussd/internal/utils"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
"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 (
|
||||
@@ -32,7 +36,7 @@ var (
|
||||
|
||||
func TestNewHandlers(t *testing.T) {
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
accountService := server.TestAccountService{}
|
||||
accountService := testservice.TestAccountService{}
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
@@ -95,7 +99,7 @@ func TestCreateAccount(t *testing.T) {
|
||||
Description: "Account creation successed",
|
||||
Result: map[string]any{
|
||||
"trackingId": "1234567890",
|
||||
"publicKey": "1235QERYU",
|
||||
"publicKey": "0xD3adB33f",
|
||||
},
|
||||
},
|
||||
expectedResult: resource.Result{
|
||||
@@ -116,9 +120,10 @@ func TestCreateAccount(t *testing.T) {
|
||||
flagManager: fm.parser,
|
||||
}
|
||||
|
||||
publicKey := tt.serverResponse.Result["publicKey"].(string)
|
||||
data := map[utils.DataTyp]string{
|
||||
utils.DATA_TRACKING_ID: tt.serverResponse.Result["trackingId"].(string),
|
||||
utils.DATA_PUBLIC_KEY: tt.serverResponse.Result["publicKey"].(string),
|
||||
utils.DATA_PUBLIC_KEY: publicKey,
|
||||
}
|
||||
|
||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_ACCOUNT_CREATED).Return([]byte(""), notFoundErr)
|
||||
@@ -127,6 +132,12 @@ func TestCreateAccount(t *testing.T) {
|
||||
for key, value := range data {
|
||||
mockDataStore.On("WriteEntry", ctx, sessionId, key, []byte(value)).Return(nil)
|
||||
}
|
||||
publicKeyNormalized, err := common.NormalizeHex(publicKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mockDataStore.On("WriteEntry", ctx, publicKeyNormalized, utils.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId)).Return(nil)
|
||||
|
||||
// Call the method you want to test
|
||||
res, err := h.CreateAccount(ctx, "create_account", []byte("some-input"))
|
||||
@@ -163,8 +174,11 @@ func TestWithPersister_PanicWhenAlreadySet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveFirstname(t *testing.T) {
|
||||
// Create a new instance of MockMyDataStore
|
||||
// Create new mocks
|
||||
mockStore := new(mocks.MockUserDataStore)
|
||||
mockState := state.NewState(16)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
|
||||
// Define test data
|
||||
sessionId := "session123"
|
||||
@@ -172,11 +186,13 @@ func TestSaveFirstname(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||
|
||||
// Set up the expected behavior of the mock
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_FIRST_NAME, []byte(firstName)).Return(nil)
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(firstName)).Return(nil)
|
||||
|
||||
// Create the Handlers instance with the mock store
|
||||
h := &Handlers{
|
||||
userdataStore: mockStore,
|
||||
flagManager: fm.parser,
|
||||
st: mockState,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
@@ -193,6 +209,9 @@ func TestSaveFirstname(t *testing.T) {
|
||||
func TestSaveFamilyname(t *testing.T) {
|
||||
// Create a new instance of UserDataStore
|
||||
mockStore := new(mocks.MockUserDataStore)
|
||||
mockState := state.NewState(16)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
|
||||
// Define test data
|
||||
sessionId := "session123"
|
||||
@@ -200,11 +219,13 @@ func TestSaveFamilyname(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||
|
||||
// Set up the expected behavior of the mock
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_FAMILY_NAME, []byte(familyName)).Return(nil)
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(familyName)).Return(nil)
|
||||
|
||||
// Create the Handlers instance with the mock store
|
||||
h := &Handlers{
|
||||
userdataStore: mockStore,
|
||||
st: mockState,
|
||||
flagManager: fm.parser,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
@@ -260,7 +281,7 @@ func TestSaveTemporaryPin(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
// Set up the expected behavior of the mock
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_PIN, []byte(tt.input)).Return(nil)
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(tt.input)).Return(nil)
|
||||
|
||||
// Call the method
|
||||
res, err := h.SaveTemporaryPin(ctx, "save_pin", tt.input)
|
||||
@@ -277,8 +298,11 @@ func TestSaveTemporaryPin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveYoB(t *testing.T) {
|
||||
// Create a new instance of MockMyDataStore
|
||||
// Create new instances
|
||||
mockStore := new(mocks.MockUserDataStore)
|
||||
mockState := state.NewState(16)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
|
||||
// Define test data
|
||||
sessionId := "session123"
|
||||
@@ -286,11 +310,13 @@ func TestSaveYoB(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||
|
||||
// Set up the expected behavior of the mock
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_YOB, []byte(yob)).Return(nil)
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(yob)).Return(nil)
|
||||
|
||||
// Create the Handlers instance with the mock store
|
||||
h := &Handlers{
|
||||
userdataStore: mockStore,
|
||||
st: mockState,
|
||||
flagManager: fm.parser,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
@@ -307,6 +333,9 @@ func TestSaveYoB(t *testing.T) {
|
||||
func TestSaveLocation(t *testing.T) {
|
||||
// Create a new instance of MockMyDataStore
|
||||
mockStore := new(mocks.MockUserDataStore)
|
||||
mockState := state.NewState(16)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
|
||||
// Define test data
|
||||
sessionId := "session123"
|
||||
@@ -314,11 +343,13 @@ func TestSaveLocation(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||
|
||||
// Set up the expected behavior of the mock
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_LOCATION, []byte(yob)).Return(nil)
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(yob)).Return(nil)
|
||||
|
||||
// Create the Handlers instance with the mock store
|
||||
h := &Handlers{
|
||||
userdataStore: mockStore,
|
||||
st: mockState,
|
||||
flagManager: fm.parser,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
@@ -335,6 +366,9 @@ func TestSaveLocation(t *testing.T) {
|
||||
func TestSaveOfferings(t *testing.T) {
|
||||
// Create a new instance of MockUserDataStore
|
||||
mockStore := new(mocks.MockUserDataStore)
|
||||
mockState := state.NewState(16)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
|
||||
// Define test data
|
||||
sessionId := "session123"
|
||||
@@ -342,11 +376,13 @@ func TestSaveOfferings(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||
|
||||
// Set up the expected behavior of the mock
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_OFFERINGS, []byte(offerings)).Return(nil)
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(offerings)).Return(nil)
|
||||
|
||||
// Create the Handlers instance with the mock store
|
||||
h := &Handlers{
|
||||
userdataStore: mockStore,
|
||||
st: mockState,
|
||||
flagManager: fm.parser,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
@@ -361,10 +397,12 @@ func TestSaveOfferings(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveGender(t *testing.T) {
|
||||
// Create a new instance of MockMyDataStore
|
||||
// Create a new mock instances
|
||||
mockStore := new(mocks.MockUserDataStore)
|
||||
mockState := state.NewState(16)
|
||||
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
// Define the session ID and context
|
||||
sessionId := "session123"
|
||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||
@@ -404,16 +442,17 @@ func TestSaveGender(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Set up expectations for the mock database
|
||||
if tt.expectCall {
|
||||
expectedKey := utils.DATA_GENDER
|
||||
expectedKey := utils.DATA_TEMPORARY_VALUE
|
||||
mockStore.On("WriteEntry", ctx, sessionId, expectedKey, []byte(tt.expectedGender)).Return(nil)
|
||||
} else {
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_GENDER, []byte(tt.expectedGender)).Return(nil)
|
||||
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)).Return(nil)
|
||||
}
|
||||
mockState.ExecPath = append(mockState.ExecPath, tt.executingSymbol)
|
||||
// Create the Handlers instance with the mock store
|
||||
h := &Handlers{
|
||||
userdataStore: mockStore,
|
||||
st: mockState,
|
||||
flagManager: fm.parser,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
@@ -424,9 +463,9 @@ func TestSaveGender(t *testing.T) {
|
||||
|
||||
// Verify expectations
|
||||
if tt.expectCall {
|
||||
mockStore.AssertCalled(t, "WriteEntry", ctx, sessionId, utils.DATA_GENDER, []byte(tt.expectedGender))
|
||||
mockStore.AssertCalled(t, "WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender))
|
||||
} else {
|
||||
mockStore.AssertNotCalled(t, "WriteEntry", ctx, sessionId, utils.DATA_GENDER, []byte(tt.expectedGender))
|
||||
mockStore.AssertNotCalled(t, "WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1002,7 +1041,7 @@ func TestVerifyCreatePin(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
typ := utils.DATA_TEMPORARY_PIN
|
||||
typ := utils.DATA_TEMPORARY_VALUE
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -1480,8 +1519,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},
|
||||
@@ -1795,7 +1834,7 @@ func TestConfirmPin(t *testing.T) {
|
||||
// Set up the expected behavior of the mock
|
||||
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(tt.temporarypin)).Return(nil)
|
||||
|
||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_PIN).Return(tt.temporarypin, nil)
|
||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE).Return(tt.temporarypin, nil)
|
||||
|
||||
//Call the function under test
|
||||
res, _ := h.ConfirmPinChange(ctx, "confirm_pin_change", tt.temporarypin)
|
||||
@@ -1810,40 +1849,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 {
|
||||
@@ -1925,3 +1930,235 @@ 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 := fmt.Sprintf("%s,%s,%s,%s", "SRF", "100", "6", "0xd4c288865Ce")
|
||||
|
||||
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(expectedData)).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",
|
||||
}
|
||||
|
||||
expectedData := fmt.Sprintf("%s,%s,%s,%s", "SRF", "200", "6", "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),
|
||||
}
|
||||
|
||||
// Mocking ReadEntry calls for temporary data retrieval
|
||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_VALUE).Return([]byte(expectedData), nil)
|
||||
|
||||
// Mocking WriteEntry calls for setting active data
|
||||
for key, value := range activeEntries {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"git.defalsify.org/vise.git/engine"
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers"
|
||||
"git.grassecon.net/urdt/ussd/internal/mocks/httpmocks"
|
||||
"git.grassecon.net/urdt/ussd/internal/testutil/mocks/httpmocks"
|
||||
)
|
||||
|
||||
// invalidRequestType is a custom type to test invalid request scenarios
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers"
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers/server"
|
||||
"git.grassecon.net/urdt/ussd/internal/storage"
|
||||
"git.grassecon.net/urdt/ussd/internal/testutil/testservice"
|
||||
"git.grassecon.net/urdt/ussd/internal/testutil/testtag"
|
||||
testdataloader "github.com/peteole/testdata-loader"
|
||||
)
|
||||
|
||||
@@ -80,12 +82,12 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if AccountService == nil {
|
||||
AccountService = &server.AccountService{}
|
||||
if testtag.AccountService == nil {
|
||||
testtag.AccountService = &server.AccountService{}
|
||||
}
|
||||
|
||||
switch AccountService.(type) {
|
||||
case *server.TestAccountService:
|
||||
switch testtag.AccountService.(type) {
|
||||
case *testservice.TestAccountService:
|
||||
go func() {
|
||||
eventChannel <- false
|
||||
}()
|
||||
@@ -98,7 +100,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
|
||||
panic("Unknown account service type")
|
||||
}
|
||||
|
||||
hl, err := lhs.GetHandler(AccountService)
|
||||
hl, err := lhs.GetHandler(testtag.AccountService)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// +build !online
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers/server"
|
||||
)
|
||||
|
||||
var (
|
||||
AccountService server.AccountServiceInterface = &server.TestAccountService{}
|
||||
)
|
||||
90
internal/testutil/testservice/TestAccountService.go
Normal file
90
internal/testutil/testservice/TestAccountService.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package testservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
||||
return &api.OKResponse{
|
||||
Ok: true,
|
||||
Description: "Account creation succeeded",
|
||||
Result: map[string]any{
|
||||
"trackingId": "075ccc86-f6ef-4d33-97d5-e91cfb37aa0d",
|
||||
"publicKey": "0x623EFAFa8868df4B934dd12a8B26CB3Dd75A7AdD",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
|
||||
balanceResponse := &models.BalanceResponse{
|
||||
Ok: true,
|
||||
Result: struct {
|
||||
Balance string `json:"balance"`
|
||||
Nonce json.Number `json:"nonce"`
|
||||
}{
|
||||
Balance: "0.003 CELO",
|
||||
Nonce: json.Number("0"),
|
||||
},
|
||||
}
|
||||
|
||||
return balanceResponse, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
|
||||
trackResponse := &models.TrackStatusResponse{
|
||||
Ok: true,
|
||||
Result: struct {
|
||||
Transaction struct {
|
||||
CreatedAt time.Time "json:\"createdAt\""
|
||||
Status string "json:\"status\""
|
||||
TransferValue json.Number "json:\"transferValue\""
|
||||
TxHash string "json:\"txHash\""
|
||||
TxType string "json:\"txType\""
|
||||
}
|
||||
}{
|
||||
Transaction: models.Transaction{
|
||||
CreatedAt: time.Now(),
|
||||
Status: "SUCCESS",
|
||||
TransferValue: json.Number("0.5"),
|
||||
TxHash: "0x123abc456def",
|
||||
TxType: "transfer",
|
||||
},
|
||||
},
|
||||
}
|
||||
return trackResponse, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
|
||||
return &api.OKResponse{
|
||||
Ok: true,
|
||||
Description: "Account creation succeeded",
|
||||
Result: map[string]any{
|
||||
"active": true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
|
||||
return &models.VoucherHoldingResponse{
|
||||
Ok: true,
|
||||
Result: models.VoucherResult{
|
||||
Holdings: []dataserviceapi.TokenHoldings{
|
||||
{
|
||||
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
|
||||
TokenSymbol: "SRF",
|
||||
TokenDecimals: "6",
|
||||
Balance: "2745987",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
12
internal/testutil/testtag/offlinetest.go
Normal file
12
internal/testutil/testtag/offlinetest.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build !online
|
||||
|
||||
package testtag
|
||||
|
||||
import (
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers/server"
|
||||
accountservice "git.grassecon.net/urdt/ussd/internal/testutil/testservice"
|
||||
)
|
||||
|
||||
var (
|
||||
AccountService server.AccountServiceInterface = &accountservice.TestAccountService{}
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build online
|
||||
|
||||
package testutil
|
||||
package testtag
|
||||
|
||||
import "git.grassecon.net/urdt/ussd/internal/handlers/server"
|
||||
|
||||
@@ -22,13 +22,14 @@ const (
|
||||
DATA_OFFERINGS
|
||||
DATA_RECIPIENT
|
||||
DATA_AMOUNT
|
||||
DATA_TEMPORARY_PIN
|
||||
DATA_TEMPORARY_VALUE
|
||||
DATA_VOUCHER_LIST
|
||||
DATA_TEMPORARY_SYM
|
||||
DATA_ACTIVE_SYM
|
||||
DATA_TEMPORARY_BAL
|
||||
DATA_ACTIVE_BAL
|
||||
DATA_ADDRESS
|
||||
DATA_PUBLIC_KEY_REVERSE
|
||||
DATA_ACTIVE_DECIMAL
|
||||
DATA_ACTIVE_ADDRESS
|
||||
|
||||
)
|
||||
|
||||
func typToBytes(typ DataTyp) []byte {
|
||||
|
||||
146
internal/utils/vouchers.go
Normal file
146
internal/utils/vouchers.go
Normal file
@@ -0,0 +1,146 @@
|
||||
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 {
|
||||
tempData := fmt.Sprintf("%s,%s,%s,%s", data.TokenSymbol, data.Balance, data.TokenDecimals, data.ContractAddress)
|
||||
|
||||
if err := store.WriteEntry(ctx, sessionId, DATA_TEMPORARY_VALUE, []byte(tempData)); 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) {
|
||||
temp_data, err := store.ReadEntry(ctx, sessionId, DATA_TEMPORARY_VALUE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values := strings.SplitN(string(temp_data), ",", 4)
|
||||
|
||||
data := &dataserviceapi.TokenHoldings{}
|
||||
|
||||
data.TokenSymbol = values[0]
|
||||
data.Balance = values[1]
|
||||
data.TokenDecimals = values[2]
|
||||
data.ContractAddress = 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
|
||||
}
|
||||
198
internal/utils/vouchers_test.go
Normal file
198
internal/utils/vouchers_test.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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
|
||||
expectedData := fmt.Sprintf("%s,%s,%s,%s", "SRF", "200", "6", "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9")
|
||||
|
||||
storedValue, err := store.ReadEntry(ctx, sessionId, DATA_TEMPORARY_VALUE)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedData, string(storedValue), "Mismatch for key %v", DATA_TEMPORARY_VALUE)
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/driver"
|
||||
"git.grassecon.net/urdt/ussd/internal/testutil"
|
||||
"git.grassecon.net/urdt/ussd/internal/testutil/driver"
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
LOAD save_familyname 0
|
||||
RELOAD save_familyname
|
||||
CATCH update_familyname flag_allow_update 1
|
||||
MOUT back 0
|
||||
HALT
|
||||
LOAD save_familyname 0
|
||||
RELOAD save_familyname
|
||||
INCMP _ 0
|
||||
INCMP pin_entry *
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
LOAD save_location 0
|
||||
CATCH update_location flag_allow_update 1
|
||||
MOUT back 0
|
||||
HALT
|
||||
LOAD save_location 0
|
||||
RELOAD save_location
|
||||
INCMP _ 0
|
||||
INCMP pin_entry *
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
LOAD save_firstname 0
|
||||
RELOAD save_firstname
|
||||
CATCH update_firstname flag_allow_update 1
|
||||
MOUT back 0
|
||||
HALT
|
||||
LOAD save_firstname 0
|
||||
RELOAD save_firstname
|
||||
INCMP _ 0
|
||||
INCMP pin_entry *
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
CATCH update_offerings flag_allow_update 1
|
||||
LOAD save_offerings 0
|
||||
MOUT back 0
|
||||
HALT
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
LOAD save_yob 0
|
||||
CATCH update_yob flag_allow_update 1
|
||||
MOUT back 0
|
||||
HALT
|
||||
LOAD verify_yob 0
|
||||
CATCH incorrect_date_format flag_incorrect_date_format 1
|
||||
LOAD save_yob 0
|
||||
RELOAD save_yob
|
||||
INCMP _ 0
|
||||
INCMP pin_entry *
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
LOAD save_gender 0
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
CATCH update_gender flag_allow_update 1
|
||||
MOVE pin_entry
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
LOAD save_gender 0
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
CATCH update_gender flag_allow_update 1
|
||||
MOVE pin_entry
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
LOAD save_gender 0
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
CATCH update_gender flag_allow_update 1
|
||||
MOVE pin_entry
|
||||
|
||||
2
services/registration/update_age
Normal file
2
services/registration/update_age
Normal file
@@ -0,0 +1,2 @@
|
||||
RELOAD save_yob
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
2
services/registration/update_familyname.vis
Normal file
2
services/registration/update_familyname.vis
Normal file
@@ -0,0 +1,2 @@
|
||||
RELOAD save_familyname
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
2
services/registration/update_firstname.vis
Normal file
2
services/registration/update_firstname.vis
Normal file
@@ -0,0 +1,2 @@
|
||||
RELOAD save_firstname
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
2
services/registration/update_gender.vis
Normal file
2
services/registration/update_gender.vis
Normal file
@@ -0,0 +1,2 @@
|
||||
RELOAD save_gender
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
2
services/registration/update_location.vis
Normal file
2
services/registration/update_location.vis
Normal file
@@ -0,0 +1,2 @@
|
||||
RELOAD save_location
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
2
services/registration/update_offerings.vis
Normal file
2
services/registration/update_offerings.vis
Normal file
@@ -0,0 +1,2 @@
|
||||
RELOAD save_offerings
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
2
services/registration/update_yob.vis
Normal file
2
services/registration/update_yob.vis
Normal file
@@ -0,0 +1,2 @@
|
||||
RELOAD save_yob
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
Reference in New Issue
Block a user