Compare commits

...

98 Commits

Author SHA1 Message Date
2587882eae Merge pull request 'pin-reset' (#139) from pin-reset into pre-mock-remove
Reviewed-on: #139
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-11-02 14:54:19 +01:00
Carlosokumu
6dbe74d12b use single temporary value 2024-11-01 16:46:09 +03:00
Carlosokumu
332074375a wrap in devtools/admin 2024-11-01 16:44:54 +03:00
Carlosokumu
5e4a9e7567 Merge branch 'master' into pin-reset 2024-11-01 16:26:29 +03:00
Carlosokumu
eb2c73dce1 remove unused setadmin store 2024-11-01 11:51:50 +03:00
Carlosokumu
7e448f739a change fs store path to root 2024-11-01 09:35:48 +03:00
Carlosokumu
0014693ba8 remove guard pin option 2024-11-01 06:39:37 +03:00
45945ae9c5 Merge pull request 'Consolidate temp data storage' (#150) from consolidate-temp-data-storage into master
Reviewed-on: #150
2024-10-31 23:47:04 +01:00
alfred-mk
d7ea8fa651 Use the DATA_TEMPORARY_VALUE for the user data 2024-11-01 01:10:58 +03:00
alfred-mk
e75aa2c1f7 Merge branch 'master' into consolidate-temp-data-storage 2024-11-01 00:45:32 +03:00
alfred-mk
63eed81d3d Merge branch 'master' into consolidate-temp-data-storage 2024-11-01 00:44:15 +03:00
alfred-mk
f4ca4454ea use a single DATA_TEMPORARY_VALUE for the PIN and voucher data 2024-11-01 00:42:23 +03:00
Carlosokumu
2704069e74 Merge branch 'master' into pin-reset 2024-10-31 21:09:48 +03:00
Carlosokumu
7d1a04f089 remove from root 2024-10-31 21:01:24 +03:00
Carlosokumu
53fa6f64ce define structure of json 2024-10-31 21:01:01 +03:00
Carlosokumu
7fa38340dd add command to initialize a list of admin numbers 2024-10-31 21:00:41 +03:00
Carlosokumu
7aab3cff8c remove seed 2024-10-31 21:00:16 +03:00
Carlosokumu
299534ccf1 define seed as a command in the devtool 2024-10-31 20:59:51 +03:00
Carlosokumu
b2655b7f11 remove seed from executable 2024-10-31 20:59:11 +03:00
Carlosokumu
5abe9b78cc attach an admin store for the phone numbers 2024-10-31 20:11:26 +03:00
Carlosokumu
12825ae08a setup adminstore in the local handler service 2024-10-31 20:10:46 +03:00
Carlosokumu
ac0b4b2ed1 pass context 2024-10-31 20:08:30 +03:00
5f666382ab Merge pull request 'profile-update-pin-check' (#149) from profile-update-pin-check into master
Reviewed-on: #149
2024-10-31 16:06:47 +01:00
Carlosokumu
ce917d9e89 update tests 2024-10-31 17:05:06 +03:00
Carlosokumu
3ce25d0e14 Merge branch 'master' into profile-update-pin-check 2024-10-31 16:42:30 +03:00
Carlosokumu
8fe8ff540b Merge branch 'master' into pin-reset 2024-10-31 16:38:28 +03:00
alfred-mk
0b4bf58107 resolved failing tests due to tempData 2024-10-31 16:31:29 +03:00
074345fcf9 Merge pull request 'voucher-data' (#138) from voucher-data into master
Reviewed-on: #138
2024-10-31 13:44:18 +01:00
alfred-mk
e6a369dcdd remove unused code 2024-10-31 14:44:42 +03:00
alfred-mk
b8bbd88078 retain the temporary data for it to be overwritten 2024-10-31 14:26:28 +03:00
Carlosokumu
d25128287e update profile information on individual node 2024-10-31 14:21:22 +03:00
Carlosokumu
c45fcda2f1 remove back option check,protect profile data update 2024-10-31 14:20:04 +03:00
Carlosokumu
211cc1f775 perform profile update in individual update node 2024-10-31 14:19:12 +03:00
Carlosokumu
981f7ca4f6 add keys to for holding temporary profile info 2024-10-31 14:14:50 +03:00
Carlosokumu
05ed236e03 add node to update individual profile information 2024-10-31 14:14:10 +03:00
Carlosokumu
767a3cd64c remove pin guard menu option 2024-10-31 09:38:51 +03:00
Carlosokumu
c4078c5280 remove extra spaces 2024-10-31 09:21:46 +03:00
Carlosokumu
d434194021 catch incorrect pin entry 2024-10-30 23:21:15 +03:00
Carlosokumu
c6ca3f6be4 fix sink error 2024-10-30 22:32:38 +03:00
Carlosokumu
8093eae61a use 0 instead of 1 for back 2024-10-30 21:26:52 +03:00
Carlosokumu
833d52a558 remove print message 2024-10-30 21:26:11 +03:00
Carlosokumu
8262e14198 Merge branch 'master' into pin-reset 2024-10-30 21:10:12 +03:00
alfred-mk
a31cac4e50 Merge branch 'master' into voucher-data 2024-10-30 18:46:21 +03:00
alfred-mk
8b399781e8 make the VoucherMetadata more descriptive 2024-10-30 18:40:03 +03:00
alfred-mk
caafe495be use the dataserviceapi structs 2024-10-30 18:30:55 +03:00
alfred-mk
66b34eaea4 get the ussd-data-service package 2024-10-30 18:21:49 +03:00
Carlosokumu
ea4c6d9314 check for phone number validity 2024-10-30 18:01:43 +03:00
Carlosokumu
7c823e07ca move to root node after on back 2024-10-30 18:01:20 +03:00
Carlosokumu
41585f831c move catch and load to next node 2024-10-30 18:00:38 +03:00
Carlosokumu
d93a26f9b0 remove function exec after HALT 2024-10-30 17:58:52 +03:00
Carlosokumu
cf523e30f8 update handler in test 2024-10-30 14:50:31 +03:00
Carlosokumu
888d3befe9 add actual pin reset functionality 2024-10-30 14:50:12 +03:00
Carlosokumu
017691a40c define structure for admin numbers 2024-10-30 14:41:14 +03:00
Carlosokumu
dc418771a7 attach an admin store for phone numbers 2024-10-30 14:33:48 +03:00
Carlosokumu
c2068db050 update handler functions 2024-10-30 14:33:22 +03:00
Carlosokumu
c42b1cd66b provide required handler functions and admin store 2024-10-30 14:32:01 +03:00
Carlosokumu
b404ae95fb setup an admin store based on fs 2024-10-30 14:30:38 +03:00
alfred-mk
adaa0c63ef Extract common db operations for the test 2024-10-30 14:12:42 +03:00
carlos
843b0d1e7e Merge pull request 'Add reverse sessionid address lookup' (#146) from lash/reverse-session into master
Reviewed-on: #146
2024-10-30 10:35:55 +01:00
lash
cb997159f7 Add reverse sessionid address lookup 2024-10-30 00:59:59 +00:00
alfred-mk
7241cdbfcb Updated the tests 2024-10-30 00:53:13 +03:00
alfred-mk
0480c02633 Moved voucher related functions to the utils package 2024-10-30 00:52:23 +03:00
Carlosokumu
0a97f610a4 catch unregistred number 2024-10-29 22:23:22 +03:00
Carlosokumu
5a0563df94 group regex,check for valid number against the regex 2024-10-29 22:17:43 +03:00
Carlosokumu
7597b96dae remove catch for unregistered number 2024-10-29 22:16:34 +03:00
Carlosokumu
f37483e2f0 use _ for back navigation 2024-10-29 22:15:31 +03:00
Carlosokumu
d0ad6395b5 add check for unregistered phone numbers 2024-10-29 17:35:42 +03:00
Carlosokumu
106983a394 use explicit back to node 2024-10-29 17:35:01 +03:00
Carlosokumu
91b85af11a add reset unregistered number 2024-10-29 17:34:34 +03:00
Carlosokumu
534d756318 catch unregistred phone numbers 2024-10-29 17:18:39 +03:00
Carlosokumu
6998c30dd1 add node to handle unregistered phone numbers 2024-10-29 17:18:01 +03:00
Carlosokumu
449f90c95b add flag to catch unregistred numbers 2024-10-29 17:17:03 +03:00
Carlosokumu
e96c874300 repeat same node on invalid option input 2024-10-29 15:02:40 +03:00
Carlosokumu
b35460d3c1 Merge branch 'master' into pin-reset 2024-10-29 14:35:17 +03:00
Carlosokumu
124049c924 add admin number defination in env 2024-10-29 14:32:17 +03:00
Carlosokumu
5fd3eb3c29 set admin privilege flag 2024-10-29 14:28:58 +03:00
Carlosokumu
d83962c0ba load admin numbers defined in the .env 2024-10-29 14:26:24 +03:00
Carlosokumu
41da099933 remove the admin flag,setup an admin store 2024-10-29 13:24:13 +03:00
Carlosokumu
c9bb93ede6 create a simple admin store for phone numbers 2024-10-29 13:15:41 +03:00
Carlosokumu
ca13d9155c replace _ with explicit back node 2024-10-29 13:15:22 +03:00
Carlosokumu
e338ce0025 load and reload only after input 2024-10-29 13:14:49 +03:00
alfred-mk
03c32fd265 Merge branch 'master' into voucher-data 2024-10-28 18:40:37 +03:00
727f54ee57 Merge pull request 'tests-refactor' (#137) from tests-refactor into master
Reviewed-on: #137
2024-10-28 16:28:41 +01:00
Carlosokumu
b97965193b add pin reset for others handling 2024-10-28 16:37:23 +03:00
Carlosokumu
aec0abb2b6 setup an admin flag 2024-10-28 16:34:33 +03:00
alfred-mk
2c361e5b96 Added tests related to the vouchers list 2024-10-28 16:30:13 +03:00
alfred-mk
131f3bcf46 Added the prefixDb to the Handlers struct~ 2024-10-28 16:27:24 +03:00
alfred-mk
520f5abdcd Added the subPrefixDb mock 2024-10-28 16:23:44 +03:00
alfred-mk
5d294b663c Added the PrefixDb interface 2024-10-28 16:21:31 +03:00
Carlosokumu
26073c8000 define handler functions required to reset others pin 2024-10-28 15:49:20 +03:00
Carlosokumu
e4c2f644f3 define a key to hold number during pin reset 2024-10-28 15:47:56 +03:00
Carlosokumu
3de46cef5e setup pin reset nodes 2024-10-28 15:45:08 +03:00
Carlosokumu
0cc0bdf9f7 add pin reset for others nodes 2024-10-28 15:19:35 +03:00
Carlosokumu
72d5c186dd add admin privilege flag 2024-10-28 15:18:40 +03:00
alfred-mk
dc782d87a8 Updated the TestSetVoucher 2024-10-28 11:20:06 +03:00
alfred-mk
ddae746b9d formatted code 2024-10-28 11:07:59 +03:00
alfred-mk
6c3ff0e9db Merge branch 'master' into voucher-data 2024-10-25 17:37:08 +03:00
alfred-mk
3ef64271e7 Store and retrieve the voucher decimals and contract address 2024-10-25 17:24:09 +03:00
61 changed files with 1252 additions and 301 deletions

View File

@@ -16,3 +16,7 @@ CREATE_ACCOUNT_URL=http://localhost:5003/api/v2/account/create
TRACK_STATUS_URL=https://custodial.sarafu.africa/api/track/ TRACK_STATUS_URL=https://custodial.sarafu.africa/api/track/
BALANCE_URL=https://custodial.sarafu.africa/api/account/status/ BALANCE_URL=https://custodial.sarafu.africa/api/account/status/
TRACK_URL=http://localhost:5003/api/v2/account/status TRACK_URL=http://localhost:5003/api/v2/account/status
#numbers with privileges to reset others pin
ADMIN_NUMBERS=254051722XXX,255012221XXX

View File

@@ -131,7 +131,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore) lhs.SetDataStore(&userdataStore)
if err != nil { if err != nil {

View File

@@ -104,8 +104,9 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore) lhs.SetDataStore(&userdataStore)
accountService := server.AccountService{} accountService := server.AccountService{}
hl, err := lhs.GetHandler(&accountService) hl, err := lhs.GetHandler(&accountService)

View File

@@ -92,13 +92,14 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore) lhs.SetDataStore(&userdataStore)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
accountService := server.AccountService{} accountService := server.AccountService{}
hl, err := lhs.GetHandler(&accountService) hl, err := lhs.GetHandler(&accountService)
if err != nil { if err != nil {

View File

@@ -88,7 +88,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdatastore) lhs.SetDataStore(&userdatastore)
lhs.SetPersister(pe) lhs.SetPersister(pe)

18
common/hex.go Normal file
View 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
}

View File

@@ -0,0 +1,7 @@
{
"admins": [
{
"phonenumber" : "<replace with any admin number to test with >"
}
]
}

View File

@@ -0,0 +1,47 @@
package commands
import (
"context"
"encoding/json"
"os"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/utils"
)
var (
logg = logging.NewVanilla().WithDomain("adminstore")
)
type Admin struct {
PhoneNumber string `json:"phonenumber"`
}
type Config struct {
Admins []Admin `json:"admins"`
}
func Seed(ctx context.Context) error {
var config Config
adminstore, err := utils.NewAdminStore(ctx, "../admin_numbers")
store := adminstore.FsStore
if err != nil {
return err
}
defer store.Close()
data, err := os.ReadFile("admin_numbers.json")
if err != nil {
return err
}
if err := json.Unmarshal(data, &config); err != nil {
return err
}
for _, admin := range config.Admins {
err := store.Put(ctx, []byte(admin.PhoneNumber), []byte("1"))
if err != nil {
logg.Printf(logging.LVL_DEBUG, "Failed to insert admin number", admin.PhoneNumber)
return err
}
}
return nil
}

17
devtools/admin/main.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"context"
"log"
"git.grassecon.net/urdt/ussd/devtools/admin/commands"
)
func main() {
ctx := context.Background()
err := commands.Seed(ctx)
if err != nil {
log.Fatalf("Failed to initialize a list of admins with error %s", err)
}
}

2
go.mod
View File

@@ -12,6 +12,8 @@ require (
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 gopkg.in/leonelquinteros/gotext.v1 v1.3.1
) )
require github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a // indirect
require ( require (
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect

2
go.sum
View File

@@ -18,6 +18,8 @@ github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU= github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU=
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo= github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a h1:q/YH7nE2j8epNmFnTu0tU1vwtCxtQ6nH+d7hRVV5krU=
github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a/go.mod h1:hdKaKwqiW6/kphK4j/BhmuRlZDLo1+DYo3gYw5O0siw=
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=

View File

@@ -1,6 +1,8 @@
package handlers package handlers
import ( import (
"context"
"git.defalsify.org/vise.git/asm" "git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/engine"
@@ -8,6 +10,7 @@ import (
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers/server" "git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd" "git.grassecon.net/urdt/ussd/internal/handlers/ussd"
"git.grassecon.net/urdt/ussd/internal/utils"
) )
type HandlerService interface { type HandlerService interface {
@@ -28,20 +31,26 @@ type LocalHandlerService struct {
DbRs *resource.DbResource DbRs *resource.DbResource
Pe *persist.Persister Pe *persist.Persister
UserdataStore *db.Db UserdataStore *db.Db
AdminStore *utils.AdminStore
Cfg engine.Config Cfg engine.Config
Rs resource.Resource Rs resource.Resource
} }
func NewLocalHandlerService(fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) { func NewLocalHandlerService(ctx context.Context, fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) {
parser, err := getParser(fp, debug) parser, err := getParser(fp, debug)
if err != nil { if err != nil {
return nil, err return nil, err
} }
adminstore, err := utils.NewAdminStore(ctx, "admin_numbers")
if err != nil {
return nil, err
}
return &LocalHandlerService{ return &LocalHandlerService{
Parser: parser, Parser: parser,
DbRs: dbResource, DbRs: dbResource,
Cfg: cfg, AdminStore: adminstore,
Rs: rs, Cfg: cfg,
Rs: rs,
}, nil }, nil
} }
@@ -54,7 +63,7 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) {
} }
func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceInterface) (*ussd.Handlers, error) { func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceInterface) (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore,accountService) ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -98,6 +107,13 @@ func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceIn
ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList) ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList)
ls.DbRs.AddLocalFunc("view_voucher", ussdHandlers.ViewVoucher) ls.DbRs.AddLocalFunc("view_voucher", ussdHandlers.ViewVoucher)
ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher) ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher)
ls.DbRs.AddLocalFunc("reset_valid_pin", ussdHandlers.ResetValidPin)
ls.DbRs.AddLocalFunc("check_pin_mismatch", ussdHandlers.CheckPinMisMatch)
ls.DbRs.AddLocalFunc("validate_blocked_number", ussdHandlers.ValidateBlockedNumber)
ls.DbRs.AddLocalFunc("retrieve_blocked_number", ussdHandlers.RetrieveBlockedNumber)
ls.DbRs.AddLocalFunc("reset_unregistered_number", ussdHandlers.ResetUnregisteredNumber)
ls.DbRs.AddLocalFunc("reset_others_pin", ussdHandlers.ResetOthersPin)
ls.DbRs.AddLocalFunc("save_others_temporary_pin", ussdHandlers.SaveOthersTemporaryPin)
return ussdHandlers, nil return ussdHandlers, nil
} }

View File

@@ -19,6 +19,7 @@ import (
"git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state" "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/handlers/server"
"git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/internal/utils"
"gopkg.in/leonelquinteros/gotext.v1" "gopkg.in/leonelquinteros/gotext.v1"
@@ -32,7 +33,12 @@ var (
translationDir = path.Join(scriptDir, "locale") translationDir = path.Join(scriptDir, "locale")
okResponse *api.OKResponse okResponse *api.OKResponse
errResponse *api.ErrResponse errResponse *api.ErrResponse
backOption = []byte("0") )
// Define the regex patterns as constants
const (
phoneRegex = `(\(\d{3}\)\s?|\d{3}[-.\s]?)?\d{3}[-.\s]?\d{4}`
pinPattern = `^\d{4}$`
) )
// FlagManager handles centralized flag management // FlagManager handles centralized flag management
@@ -63,34 +69,43 @@ type Handlers struct {
st *state.State st *state.State
ca cache.Memory ca cache.Memory
userdataStore utils.DataStore userdataStore utils.DataStore
adminstore *utils.AdminStore
flagManager *asm.FlagParser flagManager *asm.FlagParser
accountService server.AccountServiceInterface accountService server.AccountServiceInterface
prefixDb storage.PrefixDb
} }
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService server.AccountServiceInterface) (*Handlers, error) { func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService server.AccountServiceInterface) (*Handlers, error) {
if userdataStore == nil { if userdataStore == nil {
return nil, fmt.Errorf("cannot create handler with nil userdata store") return nil, fmt.Errorf("cannot create handler with nil userdata store")
} }
userDb := &utils.UserDataStore{ userDb := &utils.UserDataStore{
Db: userdataStore, Db: userdataStore,
} }
// Instantiate the SubPrefixDb with "vouchers" prefix
prefixDb := storage.NewSubPrefixDb(userdataStore, []byte("vouchers"))
h := &Handlers{ h := &Handlers{
userdataStore: userDb, userdataStore: userDb,
flagManager: appFlags, flagManager: appFlags,
adminstore: adminstore,
accountService: accountService, accountService: accountService,
prefixDb: prefixDb,
} }
return h, nil return h, nil
} }
// Define the regex pattern as a constant
const pinPattern = `^\d{4}$`
// isValidPIN checks whether the given input is a 4 digit number // isValidPIN checks whether the given input is a 4 digit number
func isValidPIN(pin string) bool { func isValidPIN(pin string) bool {
match, _ := regexp.MatchString(pinPattern, pin) match, _ := regexp.MatchString(pinPattern, pin)
return match return match
} }
func isValidPhoneNumber(phonenumber string) bool {
match, _ := regexp.MatchString(phoneRegex, phonenumber)
return match
}
func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers { func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers {
if h.pe != nil { if h.pe != nil {
panic("persister already set") panic("persister already set")
@@ -101,13 +116,25 @@ func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers {
func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var r resource.Result var r resource.Result
if h.pe == nil { if h.pe == nil {
logg.WarnCtxf(ctx, "handler init called before it is ready or more than once", "state", h.st, "cache", h.ca) logg.WarnCtxf(ctx, "handler init called before it is ready or more than once", "state", h.st, "cache", h.ca)
return r, nil return r, nil
} }
h.st = h.pe.GetState() h.st = h.pe.GetState()
h.ca = h.pe.GetMemory() h.ca = h.pe.GetMemory()
sessionId, _ := ctx.Value("SessionId").(string)
flag_admin_privilege, _ := h.flagManager.GetFlag("flag_admin_privilege")
isAdmin, _ := h.adminstore.IsAdmin(sessionId)
if isAdmin {
r.FlagSet = append(r.FlagSet, flag_admin_privilege)
} else {
r.FlagReset = append(r.FlagReset, flag_admin_privilege)
}
if h.st == nil || h.ca == nil { if h.st == nil || h.ca == nil {
logg.ErrorCtxf(ctx, "perister fail in handler", "state", h.st, "cache", h.ca) logg.ErrorCtxf(ctx, "perister fail in handler", "state", h.st, "cache", h.ca)
return r, fmt.Errorf("cannot get state and memory for handler") return r, fmt.Errorf("cannot get state and memory for handler")
@@ -154,13 +181,21 @@ func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, r
utils.DATA_TRACKING_ID: trackingId, utils.DATA_TRACKING_ID: trackingId,
utils.DATA_PUBLIC_KEY: publicKey, utils.DATA_PUBLIC_KEY: publicKey,
} }
store := h.userdataStore
for key, value := range data { 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 { if err != nil {
return err 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) res.FlagSet = append(res.FlagSet, flag_account_created)
return nil return nil
@@ -190,6 +225,30 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte)
return res, nil return res, nil
} }
func (h *Handlers) CheckPinMisMatch(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
store := h.userdataStore
blockedNumber, err := store.ReadEntry(ctx, sessionId, utils.DATA_BLOCKED_NUMBER)
if err != nil {
return res, err
}
temporaryPin, err := store.ReadEntry(ctx, string(blockedNumber), utils.DATA_TEMPORARY_VALUE)
if err != nil {
return res, err
}
if bytes.Equal(temporaryPin, input) {
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
} else {
res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
}
return res, nil
}
func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{} res := resource.Result{}
_, ok := ctx.Value("SessionId").(string) _, ok := ctx.Value("SessionId").(string)
@@ -208,7 +267,7 @@ func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (
return res, nil 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 // during the account creation process
// and during the change PIN process // and during the change PIN process
func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
@@ -221,7 +280,6 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt
} }
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
accountPIN := string(input) accountPIN := string(input)
// Validate that the PIN is a 4-digit number // Validate that the PIN is a 4-digit number
@@ -229,11 +287,32 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt
res.FlagSet = append(res.FlagSet, flag_incorrect_pin) res.FlagSet = append(res.FlagSet, flag_incorrect_pin)
return res, nil return res, nil
} }
res.FlagReset = append(res.FlagReset, flag_incorrect_pin) res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(accountPIN))
if err != nil {
return res, err
}
return res, nil
}
func (h *Handlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN, []byte(accountPIN)) sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
temporaryPin := string(input)
blockedNumber, err := store.ReadEntry(ctx, sessionId, utils.DATA_BLOCKED_NUMBER)
if err != nil {
return res, err
}
err = store.WriteEntry(ctx, string(blockedNumber), utils.DATA_TEMPORARY_VALUE, []byte(temporaryPin))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -250,7 +329,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
store := h.userdataStore 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 { if err != nil {
return res, err return res, err
} }
@@ -281,11 +360,10 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore 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 { if err != nil {
return res, err return res, err
} }
if bytes.Equal(input, temporaryPin) { if bytes.Equal(input, temporaryPin) {
res.FlagSet = []uint32{flag_valid_pin} res.FlagSet = []uint32{flag_valid_pin}
res.FlagReset = []uint32{flag_pin_mismatch} res.FlagReset = []uint32{flag_pin_mismatch}
@@ -320,13 +398,18 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
if len(input) > 0 { firstName := string(input)
if bytes.Equal(input, backOption) { store := h.userdataStore
return res, nil 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
} }
firstName := string(input) } else {
store := h.userdataStore err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(firstName))
err = store.WriteEntry(ctx, sessionId, utils.DATA_FIRST_NAME, []byte(firstName))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -343,20 +426,24 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
if len(input) > 0 { store := h.userdataStore
if bytes.Equal(input, backOption) { familyName := string(input)
return res, nil
} flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
familyName := string(input) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_FAMILY_NAME, []byte(familyName)) 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 { if err != nil {
return res, err return res, err
} }
} else { } 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 return res, nil
} }
@@ -368,10 +455,19 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
if len(input) == 4 { yob := string(input)
yob := string(input) store := h.userdataStore
store := h.userdataStore flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
err = store.WriteEntry(ctx, sessionId, utils.DATA_YOB, []byte(yob)) 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 { if err != nil {
return res, err return res, err
} }
@@ -388,13 +484,20 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
if len(input) > 0 { location := string(input)
if bytes.Equal(input, backOption) { store := h.userdataStore
return res, nil
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
} }
location := string(input) } else {
store := h.userdataStore err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(location))
err = store.WriteEntry(ctx, sessionId, utils.DATA_LOCATION, []byte(location))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -412,14 +515,22 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
if bytes.Equal(input, backOption) {
return res, nil
}
gender := strings.Split(symbol, "_")[1] gender := strings.Split(symbol, "_")[1]
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(gender)) flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
if err != nil { allowUpdate := h.st.MatchFlag(flag_allow_update, true)
return res, nil
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 return res, nil
@@ -433,12 +544,22 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
if len(input) > 0 { offerings := string(input)
offerings := string(input) store := h.userdataStore
store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_OFFERINGS, []byte(offerings)) 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 { 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
} }
} }
@@ -455,6 +576,14 @@ func (h *Handlers) ResetAllowUpdate(ctx context.Context, sym string, input []byt
return res, nil return res, nil
} }
// ResetAllowUpdate resets the allowupdate flag that allows a user to update profile data.
func (h *Handlers) ResetValidPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
res.FlagReset = append(res.FlagReset, flag_valid_pin)
return res, nil
}
// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry. // ResetAccountAuthorized resets the account authorization flag after a successful PIN entry.
func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@@ -538,11 +667,13 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, err return res, err
} }
okResponse, err = h.accountService.TrackAccountStatus(ctx, string(publicKey)) okResponse, err = h.accountService.TrackAccountStatus(ctx, string(publicKey))
if err != nil { if err != nil {
res.FlagSet = append(res.FlagSet, flag_api_error) res.FlagSet = append(res.FlagSet, flag_api_error)
@@ -599,7 +730,6 @@ func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (res
var err error var err error
flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
date := string(input) date := string(input)
_, err = strconv.Atoi(date) _, err = strconv.Atoi(date)
if err != nil { if err != nil {
@@ -622,7 +752,6 @@ func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []by
var res resource.Result var res resource.Result
flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
return res, nil return res, nil
} }
@@ -705,6 +834,67 @@ func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input
return res, nil return res, nil
} }
func (h *Handlers) ResetOthersPin(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")
}
blockedPhonenumber, err := store.ReadEntry(ctx, sessionId, utils.DATA_BLOCKED_NUMBER)
if err != nil {
return res, err
}
temporaryPin, err := store.ReadEntry(ctx, string(blockedPhonenumber), utils.DATA_TEMPORARY_VALUE)
if err != nil {
return res, err
}
err = store.WriteEntry(ctx, string(blockedPhonenumber), utils.DATA_ACCOUNT_PIN, []byte(temporaryPin))
if err != nil {
return res, nil
}
return res, nil
}
func (h *Handlers) ResetUnregisteredNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number")
res.FlagReset = append(res.FlagReset, flag_unregistered_number)
return res, nil
}
func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number")
store := h.userdataStore
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
blockedNumber := string(input)
_, err = store.ReadEntry(ctx, blockedNumber, utils.DATA_PUBLIC_KEY)
if !isValidPhoneNumber(blockedNumber) {
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
return res, nil
}
if err != nil {
if db.IsNotFound(err) {
logg.Printf(logging.LVL_INFO, "Invalid or unregistered number")
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
return res, nil
} else {
return res, err
}
}
err = store.WriteEntry(ctx, sessionId, utils.DATA_BLOCKED_NUMBER, []byte(blockedNumber))
if err != nil {
return res, nil
}
return res, nil
}
// ValidateRecipient validates that the given input is a valid phone number. // ValidateRecipient validates that the given input is a valid phone number.
func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@@ -876,6 +1066,22 @@ func (h *Handlers) GetRecipient(ctx context.Context, sym string, input []byte) (
return res, nil return res, nil
} }
// RetrieveBlockedNumber gets the current number during the pin reset for other's is in progress.
func (h *Handlers) RetrieveBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
store := h.userdataStore
blockedNumber, _ := store.ReadEntry(ctx, sessionId, utils.DATA_BLOCKED_NUMBER)
res.Content = string(blockedNumber)
return res, nil
}
// GetSender returns the sessionId (phoneNumber) // GetSender returns the sessionId (phoneNumber)
func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@@ -1042,13 +1248,13 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by
if db.IsNotFound(err) { if db.IsNotFound(err) {
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, nil return res, err
} }
// Fetch vouchers from the API using the public key // Fetch vouchers from the API using the public key
vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey)) vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
if err != nil { if err != nil {
return res, nil return res, err
} }
// Return if there is no voucher // Return if there is no voucher
@@ -1105,54 +1311,33 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
return res, nil return res, nil
} }
// process voucher data data := utils.ProcessVouchers(vouchersResp.Result.Holdings)
voucherSymbolList, voucherBalanceList := ProcessVouchers(vouchersResp.Result.Holdings)
prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers")) // Store all voucher data
err = prefixdb.Put(ctx, []byte("sym"), []byte(voucherSymbolList)) dataMap := map[string]string{
if err != nil { "sym": data.Symbols,
return res, nil "bal": data.Balances,
"deci": data.Decimals,
"addr": data.Addresses,
} }
err = prefixdb.Put(ctx, []byte("bal"), []byte(voucherBalanceList)) for key, value := range dataMap {
if err != nil { if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
return res, nil return res, nil
}
} }
return res, nil return res, nil
} }
// ProcessVouchers formats the holdings into symbol and balance lists.
func ProcessVouchers(holdings []struct {
ContractAddress string `json:"contractAddress"`
TokenSymbol string `json:"tokenSymbol"`
TokenDecimals string `json:"tokenDecimals"`
Balance string `json:"balance"`
}) (string, string) {
var numberedSymbols, numberedBalances []string
for i, voucher := range holdings {
numberedSymbols = append(numberedSymbols, fmt.Sprintf("%d:%s", i+1, voucher.TokenSymbol))
numberedBalances = append(numberedBalances, fmt.Sprintf("%d:%s", i+1, voucher.Balance))
}
voucherSymbolList := strings.Join(numberedSymbols, "\n")
voucherBalanceList := strings.Join(numberedBalances, "\n")
return voucherSymbolList, voucherBalanceList
}
// GetVoucherList fetches the list of vouchers and formats them // GetVoucherList fetches the list of vouchers and formats them
func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
// Read vouchers from the store // Read vouchers from the store
store := h.userdataStore voucherData, err := h.prefixDb.Get(ctx, []byte("sym"))
prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers"))
voucherData, err := prefixdb.Get(ctx, []byte("sym"))
if err != nil { if err != nil {
return res, nil return res, err
} }
res.Content = string(voucherData) res.Content = string(voucherData)
@@ -1163,8 +1348,6 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte)
// ViewVoucher retrieves the token holding and balance from the subprefixDB // ViewVoucher retrieves the token holding and balance from the subprefixDB
func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
store := h.userdataStore
sessionId, ok := ctx.Value("SessionId").(string) sessionId, ok := ctx.Value("SessionId").(string)
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
@@ -1173,126 +1356,51 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
inputStr := string(input) inputStr := string(input)
if inputStr == "0" || inputStr == "99" { if inputStr == "0" || inputStr == "99" {
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
return res, nil return res, nil
} }
prefixdb := storage.NewSubPrefixDb(store, []byte("vouchers")) metadata, err := utils.GetVoucherData(ctx, h.prefixDb, inputStr)
// Retrieve the voucher symbol list
voucherSymbolList, err := prefixdb.Get(ctx, []byte("sym"))
if err != nil { if err != nil {
return res, fmt.Errorf("failed to retrieve voucher symbol list: %v", err) return res, fmt.Errorf("failed to retrieve voucher data: %v", err)
} }
// Retrieve the voucher balance list if metadata == nil {
voucherBalanceList, err := prefixdb.Get(ctx, []byte("bal"))
if err != nil {
return res, fmt.Errorf("failed to retrieve voucher balance list: %v", err)
}
// match the voucher symbol and balance with the input
matchedSymbol, matchedBalance := MatchVoucher(inputStr, string(voucherSymbolList), string(voucherBalanceList))
// If a match is found, write the temporary sym and balance
if matchedSymbol != "" && matchedBalance != "" {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte(matchedSymbol))
if err != nil {
return res, err
}
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte(matchedBalance))
if err != nil {
return res, err
}
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
res.Content = fmt.Sprintf("%s\n%s", matchedSymbol, matchedBalance)
} else {
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher) res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
return res, nil
} }
if err := utils.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil {
return res, err
}
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
res.Content = fmt.Sprintf("%s\n%s", metadata.TokenSymbol, metadata.Balance)
return res, nil return res, nil
} }
// MatchVoucher finds the matching voucher symbol and balance based on the input. // SetVoucher retrieves the temp voucher data and sets it as the active data
func MatchVoucher(inputStr string, voucherSymbols, voucherBalances string) (string, string) {
// Split the lists into slices for processing
symbols := strings.Split(voucherSymbols, "\n")
balances := strings.Split(voucherBalances, "\n")
var matchedSymbol, matchedBalance string
for i, symbol := range symbols {
symbolParts := strings.SplitN(symbol, ":", 2)
if len(symbolParts) != 2 {
continue
}
voucherNum := symbolParts[0]
voucherSymbol := symbolParts[1]
// Check if input matches either the number or the symbol
if inputStr == voucherNum || strings.EqualFold(inputStr, voucherSymbol) {
matchedSymbol = voucherSymbol
// Ensure there's a corresponding balance
if i < len(balances) {
matchedBalance = strings.SplitN(balances[i], ":", 2)[1]
}
break
}
}
return matchedSymbol, matchedBalance
}
// SetVoucher retrieves the temporary sym and balance,
// sets them as the active data and
// clears the temporary data
func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
var err error
store := h.userdataStore
sessionId, ok := ctx.Value("SessionId").(string) sessionId, ok := ctx.Value("SessionId").(string)
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
// get the current temporary symbol // Get temporary data
temporarySym, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_SYM) tempData, err := utils.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId)
if err != nil {
return res, err
}
// get the current temporary balance
temporaryBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_BAL)
if err != nil { if err != nil {
return res, err return res, err
} }
// set the active symbol // Set as active and clear temporary data
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(temporarySym)) if err := utils.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil {
if err != nil {
return res, err
}
// set the active balance
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL, []byte(temporaryBal))
if err != nil {
return res, err return res, err
} }
// reset the temporary symbol res.Content = tempData.TokenSymbol
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_SYM, []byte(""))
if err != nil {
return res, err
}
// reset the temporary balance
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_BAL, []byte(""))
if err != nil {
return res, err
}
res.Content = string(temporarySym)
return res, nil return res, nil
} }

View File

@@ -19,11 +19,14 @@ import (
"git.grassecon.net/urdt/ussd/internal/testutil/mocks" "git.grassecon.net/urdt/ussd/internal/testutil/mocks"
"git.grassecon.net/urdt/ussd/internal/testutil/testservice" "git.grassecon.net/urdt/ussd/internal/testutil/testservice"
"git.grassecon.net/urdt/ussd/common"
"git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/internal/utils"
"github.com/alecthomas/assert/v2" "github.com/alecthomas/assert/v2"
"github.com/grassrootseconomics/eth-custodial/pkg/api" "github.com/grassrootseconomics/eth-custodial/pkg/api"
testdataloader "github.com/peteole/testdata-loader" testdataloader "github.com/peteole/testdata-loader"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
var ( var (
@@ -39,7 +42,7 @@ func TestNewHandlers(t *testing.T) {
} }
t.Run("Valid UserDataStore", func(t *testing.T) { t.Run("Valid UserDataStore", func(t *testing.T) {
mockStore := &mocks.MockUserDataStore{} mockStore := &mocks.MockUserDataStore{}
handlers, err := NewHandlers(fm.parser, mockStore, &accountService) handlers, err := NewHandlers(fm.parser, mockStore, nil, &accountService)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
@@ -55,7 +58,7 @@ func TestNewHandlers(t *testing.T) {
t.Run("Nil UserDataStore", func(t *testing.T) { t.Run("Nil UserDataStore", func(t *testing.T) {
appFlags := &asm.FlagParser{} appFlags := &asm.FlagParser{}
handlers, err := NewHandlers(appFlags, nil, &accountService) handlers, err := NewHandlers(appFlags, nil, nil, &accountService)
if err == nil { if err == nil {
t.Fatal("expected an error, got none") t.Fatal("expected an error, got none")
@@ -96,7 +99,7 @@ func TestCreateAccount(t *testing.T) {
Description: "Account creation successed", Description: "Account creation successed",
Result: map[string]any{ Result: map[string]any{
"trackingId": "1234567890", "trackingId": "1234567890",
"publicKey": "1235QERYU", "publicKey": "0xD3adB33f",
}, },
}, },
expectedResult: resource.Result{ expectedResult: resource.Result{
@@ -117,9 +120,10 @@ func TestCreateAccount(t *testing.T) {
flagManager: fm.parser, flagManager: fm.parser,
} }
publicKey := tt.serverResponse.Result["publicKey"].(string)
data := map[utils.DataTyp]string{ data := map[utils.DataTyp]string{
utils.DATA_TRACKING_ID: tt.serverResponse.Result["trackingId"].(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) mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_ACCOUNT_CREATED).Return([]byte(""), notFoundErr)
@@ -128,6 +132,12 @@ func TestCreateAccount(t *testing.T) {
for key, value := range data { for key, value := range data {
mockDataStore.On("WriteEntry", ctx, sessionId, key, []byte(value)).Return(nil) 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 // Call the method you want to test
res, err := h.CreateAccount(ctx, "create_account", []byte("some-input")) res, err := h.CreateAccount(ctx, "create_account", []byte("some-input"))
@@ -164,8 +174,11 @@ func TestWithPersister_PanicWhenAlreadySet(t *testing.T) {
} }
func TestSaveFirstname(t *testing.T) { func TestSaveFirstname(t *testing.T) {
// Create a new instance of MockMyDataStore // Create new mocks
mockStore := new(mocks.MockUserDataStore) mockStore := new(mocks.MockUserDataStore)
mockState := state.NewState(16)
fm, err := NewFlagManager(flagsPath)
// Define test data // Define test data
sessionId := "session123" sessionId := "session123"
@@ -173,11 +186,13 @@ func TestSaveFirstname(t *testing.T) {
ctx := context.WithValue(context.Background(), "SessionId", sessionId) ctx := context.WithValue(context.Background(), "SessionId", sessionId)
// Set up the expected behavior of the mock // 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 // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: mockStore, userdataStore: mockStore,
flagManager: fm.parser,
st: mockState,
} }
// Call the method // Call the method
@@ -194,6 +209,9 @@ func TestSaveFirstname(t *testing.T) {
func TestSaveFamilyname(t *testing.T) { func TestSaveFamilyname(t *testing.T) {
// Create a new instance of UserDataStore // Create a new instance of UserDataStore
mockStore := new(mocks.MockUserDataStore) mockStore := new(mocks.MockUserDataStore)
mockState := state.NewState(16)
fm, err := NewFlagManager(flagsPath)
// Define test data // Define test data
sessionId := "session123" sessionId := "session123"
@@ -201,11 +219,13 @@ func TestSaveFamilyname(t *testing.T) {
ctx := context.WithValue(context.Background(), "SessionId", sessionId) ctx := context.WithValue(context.Background(), "SessionId", sessionId)
// Set up the expected behavior of the mock // 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 // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: mockStore, userdataStore: mockStore,
st: mockState,
flagManager: fm.parser,
} }
// Call the method // Call the method
@@ -261,7 +281,7 @@ func TestSaveTemporaryPin(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Set up the expected behavior of the mock // 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 // Call the method
res, err := h.SaveTemporaryPin(ctx, "save_pin", tt.input) res, err := h.SaveTemporaryPin(ctx, "save_pin", tt.input)
@@ -278,8 +298,11 @@ func TestSaveTemporaryPin(t *testing.T) {
} }
func TestSaveYoB(t *testing.T) { func TestSaveYoB(t *testing.T) {
// Create a new instance of MockMyDataStore // Create new instances
mockStore := new(mocks.MockUserDataStore) mockStore := new(mocks.MockUserDataStore)
mockState := state.NewState(16)
fm, err := NewFlagManager(flagsPath)
// Define test data // Define test data
sessionId := "session123" sessionId := "session123"
@@ -287,11 +310,13 @@ func TestSaveYoB(t *testing.T) {
ctx := context.WithValue(context.Background(), "SessionId", sessionId) ctx := context.WithValue(context.Background(), "SessionId", sessionId)
// Set up the expected behavior of the mock // 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 // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: mockStore, userdataStore: mockStore,
st: mockState,
flagManager: fm.parser,
} }
// Call the method // Call the method
@@ -308,6 +333,9 @@ func TestSaveYoB(t *testing.T) {
func TestSaveLocation(t *testing.T) { func TestSaveLocation(t *testing.T) {
// Create a new instance of MockMyDataStore // Create a new instance of MockMyDataStore
mockStore := new(mocks.MockUserDataStore) mockStore := new(mocks.MockUserDataStore)
mockState := state.NewState(16)
fm, err := NewFlagManager(flagsPath)
// Define test data // Define test data
sessionId := "session123" sessionId := "session123"
@@ -315,11 +343,13 @@ func TestSaveLocation(t *testing.T) {
ctx := context.WithValue(context.Background(), "SessionId", sessionId) ctx := context.WithValue(context.Background(), "SessionId", sessionId)
// Set up the expected behavior of the mock // 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 // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: mockStore, userdataStore: mockStore,
st: mockState,
flagManager: fm.parser,
} }
// Call the method // Call the method
@@ -336,6 +366,9 @@ func TestSaveLocation(t *testing.T) {
func TestSaveOfferings(t *testing.T) { func TestSaveOfferings(t *testing.T) {
// Create a new instance of MockUserDataStore // Create a new instance of MockUserDataStore
mockStore := new(mocks.MockUserDataStore) mockStore := new(mocks.MockUserDataStore)
mockState := state.NewState(16)
fm, err := NewFlagManager(flagsPath)
// Define test data // Define test data
sessionId := "session123" sessionId := "session123"
@@ -343,11 +376,13 @@ func TestSaveOfferings(t *testing.T) {
ctx := context.WithValue(context.Background(), "SessionId", sessionId) ctx := context.WithValue(context.Background(), "SessionId", sessionId)
// Set up the expected behavior of the mock // 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 // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: mockStore, userdataStore: mockStore,
st: mockState,
flagManager: fm.parser,
} }
// Call the method // Call the method
@@ -362,10 +397,12 @@ func TestSaveOfferings(t *testing.T) {
} }
func TestSaveGender(t *testing.T) { func TestSaveGender(t *testing.T) {
// Create a new instance of MockMyDataStore // Create a new mock instances
mockStore := new(mocks.MockUserDataStore) mockStore := new(mocks.MockUserDataStore)
mockState := state.NewState(16) mockState := state.NewState(16)
fm, _ := NewFlagManager(flagsPath)
// Define the session ID and context // Define the session ID and context
sessionId := "session123" sessionId := "session123"
ctx := context.WithValue(context.Background(), "SessionId", sessionId) ctx := context.WithValue(context.Background(), "SessionId", sessionId)
@@ -405,16 +442,17 @@ func TestSaveGender(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Set up expectations for the mock database // Set up expectations for the mock database
if tt.expectCall { if tt.expectCall {
expectedKey := utils.DATA_GENDER expectedKey := utils.DATA_TEMPORARY_VALUE
mockStore.On("WriteEntry", ctx, sessionId, expectedKey, []byte(tt.expectedGender)).Return(nil) mockStore.On("WriteEntry", ctx, sessionId, expectedKey, []byte(tt.expectedGender)).Return(nil)
} else { } 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) mockState.ExecPath = append(mockState.ExecPath, tt.executingSymbol)
// Create the Handlers instance with the mock store // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: mockStore, userdataStore: mockStore,
st: mockState, st: mockState,
flagManager: fm.parser,
} }
// Call the method // Call the method
@@ -425,9 +463,9 @@ func TestSaveGender(t *testing.T) {
// Verify expectations // Verify expectations
if tt.expectCall { 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 { } 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))
} }
}) })
} }
@@ -1003,7 +1041,7 @@ func TestVerifyCreatePin(t *testing.T) {
}, },
} }
typ := utils.DATA_TEMPORARY_PIN typ := utils.DATA_TEMPORARY_VALUE
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@@ -1481,8 +1519,8 @@ func TestValidateAmount(t *testing.T) {
}, },
}, },
{ {
name: "Test with invalid amount format", name: "Test with invalid amount format",
input: []byte("0.02ms"), input: []byte("0.02ms"),
activeBal: []byte("5"), activeBal: []byte("5"),
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagSet: []uint32{flag_invalid_amount}, FlagSet: []uint32{flag_invalid_amount},
@@ -1796,7 +1834,7 @@ func TestConfirmPin(t *testing.T) {
// Set up the expected behavior of the mock // Set up the expected behavior of the mock
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(tt.temporarypin)).Return(nil) 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 //Call the function under test
res, _ := h.ConfirmPinChange(ctx, "confirm_pin_change", tt.temporarypin) res, _ := h.ConfirmPinChange(ctx, "confirm_pin_change", tt.temporarypin)
@@ -1811,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) { func TestFetchCustodialBalances(t *testing.T) {
fm, err := NewFlagManager(flagsPath) fm, err := NewFlagManager(flagsPath)
if err != nil { if err != nil {
@@ -1926,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)
}

View File

@@ -1,15 +1,14 @@
package models package models
// VoucherHoldingResponse represents a single voucher holding import dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
type VoucherHoldingResponse struct { type VoucherHoldingResponse struct {
Ok bool `json:"ok"` Ok bool `json:"ok"`
Description string `json:"description"` Description string `json:"description"`
Result struct { Result VoucherResult `json:"result"`
Holdings []struct { }
ContractAddress string `json:"contractAddress"`
TokenSymbol string `json:"tokenSymbol"` // VoucherResult holds the list of token holdings
TokenDecimals string `json:"tokenDecimals"` type VoucherResult struct {
Balance string `json:"balance"` Holdings []dataserviceapi.TokenHoldings `json:"holdings"`
} `json:"holdings"`
} `json:"result"`
} }

View File

@@ -10,6 +10,14 @@ const (
DATATYPE_USERSUB = 64 DATATYPE_USERSUB = 64
) )
// PrefixDb interface abstracts the database operations.
type PrefixDb interface {
Get(ctx context.Context, key []byte) ([]byte, error)
Put(ctx context.Context, key []byte, val []byte) error
}
var _ PrefixDb = (*SubPrefixDb)(nil)
type SubPrefixDb struct { type SubPrefixDb struct {
store db.Db store db.Db
pfx []byte pfx []byte
@@ -29,11 +37,7 @@ func (s *SubPrefixDb) toKey(k []byte) []byte {
func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) { func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
s.store.SetPrefix(DATATYPE_USERSUB) s.store.SetPrefix(DATATYPE_USERSUB)
key = s.toKey(key) key = s.toKey(key)
v, err := s.store.Get(ctx, key) return s.store.Get(ctx, key)
if err != nil {
return nil, err
}
return v, nil
} }
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error { func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {

View File

@@ -73,7 +73,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userDataStore) lhs.SetDataStore(&userDataStore)
lhs.SetPersister(pe) lhs.SetPersister(pe)

View 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)
}

View File

@@ -7,6 +7,7 @@ import (
"git.grassecon.net/urdt/ussd/internal/models" "git.grassecon.net/urdt/ussd/internal/models"
"github.com/grassrootseconomics/eth-custodial/pkg/api" "github.com/grassrootseconomics/eth-custodial/pkg/api"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
type TestAccountService struct { type TestAccountService struct {
@@ -75,20 +76,8 @@ func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) { func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
return &models.VoucherHoldingResponse{ return &models.VoucherHoldingResponse{
Ok: true, Ok: true,
Result: struct { Result: models.VoucherResult{
Holdings []struct { Holdings: []dataserviceapi.TokenHoldings{
ContractAddress string `json:"contractAddress"`
TokenSymbol string `json:"tokenSymbol"`
TokenDecimals string `json:"tokenDecimals"`
Balance string `json:"balance"`
} `json:"holdings"`
}{
Holdings: []struct {
ContractAddress string `json:"contractAddress"`
TokenSymbol string `json:"tokenSymbol"`
TokenDecimals string `json:"tokenDecimals"`
Balance string `json:"balance"`
}{
{ {
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee", ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
TokenSymbol: "SRF", TokenSymbol: "SRF",

View File

@@ -0,0 +1,51 @@
package utils
import (
"context"
"git.defalsify.org/vise.git/db"
fsdb "git.defalsify.org/vise.git/db/fs"
"git.defalsify.org/vise.git/logging"
)
var (
logg = logging.NewVanilla().WithDomain("adminstore")
)
type AdminStore struct {
ctx context.Context
FsStore db.Db
}
func NewAdminStore(ctx context.Context, fileName string) (*AdminStore, error) {
fsStore, err := getFsStore(ctx, fileName)
if err != nil {
return nil, err
}
return &AdminStore{ctx: ctx, FsStore: fsStore}, nil
}
func getFsStore(ctx context.Context, connectStr string) (db.Db, error) {
fsStore := fsdb.NewFsDb()
err := fsStore.Connect(ctx, connectStr)
fsStore.SetPrefix(db.DATATYPE_USERDATA)
if err != nil {
return nil, err
}
return fsStore, nil
}
// Checks if the given sessionId is listed as an admin.
func (as *AdminStore) IsAdmin(sessionId string) (bool, error) {
_, err := as.FsStore.Get(as.ctx, []byte(sessionId))
if err != nil {
if db.IsNotFound(err) {
logg.Printf(logging.LVL_INFO, "Returning false because session id was not found")
return false, nil
} else {
return false, err
}
}
return true, nil
}

View File

@@ -22,12 +22,15 @@ const (
DATA_OFFERINGS DATA_OFFERINGS
DATA_RECIPIENT DATA_RECIPIENT
DATA_AMOUNT DATA_AMOUNT
DATA_TEMPORARY_PIN DATA_TEMPORARY_VALUE
DATA_VOUCHER_LIST DATA_VOUCHER_LIST
DATA_TEMPORARY_SYM
DATA_ACTIVE_SYM DATA_ACTIVE_SYM
DATA_TEMPORARY_BAL
DATA_ACTIVE_BAL DATA_ACTIVE_BAL
DATA_BLOCKED_NUMBER
DATA_PUBLIC_KEY_REVERSE
DATA_ACTIVE_DECIMAL
DATA_ACTIVE_ADDRESS
) )
func typToBytes(typ DataTyp) []byte { func typToBytes(typ DataTyp) []byte {

146
internal/utils/vouchers.go Normal file
View 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
}

View 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)
}
}

View File

@@ -13,7 +13,7 @@
}, },
{ {
"input": "5", "input": "5",
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n3:Guard my PIN\n0:Back" "expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
}, },
{ {
"input": "1", "input": "1",

View File

@@ -0,0 +1 @@
Please confirm new PIN for:{{.retrieve_blocked_number}}

View File

@@ -0,0 +1,14 @@
CATCH pin_entry flag_incorrect_pin 1
RELOAD retrieve_blocked_number
MAP retrieve_blocked_number
CATCH invalid_others_pin flag_valid_pin 0
CATCH pin_reset_result flag_account_authorized 1
LOAD save_others_temporary_pin 6
RELOAD save_others_temporary_pin
MOUT back 0
HALT
INCMP _ 0
LOAD check_pin_mismatch 0
RELOAD check_pin_mismatch
CATCH others_pin_mismatch flag_pin_mismatch 1
INCMP pin_entry *

View File

@@ -3,5 +3,3 @@ MOUT back 0
HALT HALT
INCMP _ 0 INCMP _ 0
INCMP * pin_reset_success INCMP * pin_reset_success

View File

@@ -1,5 +1,5 @@
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH profile_update_success flag_allow_update 1 CATCH update_familyname flag_allow_update 1
MOUT back 0 MOUT back 0
HALT HALT
LOAD save_familyname 0 LOAD save_familyname 0

View File

@@ -1,5 +1,5 @@
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH profile_update_success flag_allow_update 1 CATCH update_location flag_allow_update 1
MOUT back 0 MOUT back 0
HALT HALT
LOAD save_location 0 LOAD save_location 0

View File

@@ -1,5 +1,5 @@
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH profile_update_success flag_allow_update 1 CATCH update_firstname flag_allow_update 1
MOUT back 0 MOUT back 0
HALT HALT
LOAD save_firstname 0 LOAD save_firstname 0

View File

@@ -1,5 +1,5 @@
CATCH incorrect_pin flag_incorrect_pin 1 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 LOAD save_offerings 0
MOUT back 0 MOUT back 0
HALT HALT

View File

@@ -0,0 +1 @@
Enter other's phone number:

View File

@@ -0,0 +1,7 @@
CATCH no_admin_privilege flag_admin_privilege 0
LOAD reset_account_authorized 0
RELOAD reset_account_authorized
MOUT back 0
HALT
INCMP _ 0
INCMP enter_others_new_pin *

View File

@@ -0,0 +1 @@
Please enter new PIN for: {{.retrieve_blocked_number}}

View File

@@ -0,0 +1,12 @@
LOAD validate_blocked_number 6
RELOAD validate_blocked_number
CATCH unregistered_number flag_unregistered_number 1
LOAD retrieve_blocked_number 0
RELOAD retrieve_blocked_number
MAP retrieve_blocked_number
MOUT back 0
HALT
LOAD verify_new_pin 6
RELOAD verify_new_pin
INCMP _ 0
INCMP * confirm_others_new_pin

View File

@@ -1,10 +1,10 @@
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH profile_update_success flag_allow_update 1 CATCH update_yob flag_allow_update 1
LOAD save_yob 0
MOUT back 0 MOUT back 0
HALT HALT
LOAD verify_yob 0 LOAD verify_yob 0
CATCH incorrect_date_format flag_incorrect_date_format 1 CATCH incorrect_date_format flag_incorrect_date_format 1
LOAD save_yob 0
RELOAD save_yob RELOAD save_yob
INCMP _ 0 INCMP _ 0
INCMP pin_entry * INCMP pin_entry *

View File

@@ -1 +0,0 @@
Guard my PIN

View File

@@ -1 +0,0 @@
Linda PIN yangu

View File

@@ -0,0 +1 @@
The PIN you have entered is invalid.Please try a 4 digit number instead.

View File

@@ -0,0 +1,5 @@
MOUT retry 1
MOUT quit 9
HALT
INCMP enter_others_new_pin 1
INCMP quit 9

View File

@@ -0,0 +1 @@
You do not have privileges to perform this action

View File

@@ -0,0 +1,5 @@
MOUT quit 9
MOUT back 0
HALT
INCMP pin_management 0
INCMP quit 9

View File

@@ -0,0 +1 @@
The PIN you have entered is not a match

View File

@@ -0,0 +1,5 @@
MOUT retry 1
MOUT quit 9
HALT
INCMP _ 1
INCMP quit 9

View File

@@ -1,8 +1,8 @@
MOUT change_pin 1 MOUT change_pin 1
MOUT reset_pin 2 MOUT reset_pin 2
MOUT guard_pin 3
MOUT back 0 MOUT back 0
HALT HALT
INCMP _ 0 INCMP my_account 0
INCMP old_pin 1 INCMP old_pin 1
INCMP enter_other_number 2
INCMP . *

View File

@@ -0,0 +1 @@
PIN reset request for {{.retrieve_blocked_number}} was successful

View File

@@ -0,0 +1,8 @@
LOAD retrieve_blocked_number 0
MAP retrieve_blocked_number
LOAD reset_others_pin 6
MOUT back 0
MOUT quit 9
HALT
INCMP pin_management 0
INCMP quit 9

View File

@@ -6,5 +6,3 @@ MOUT quit 9
HALT HALT
INCMP main 0 INCMP main 0
INCMP quit 9 INCMP quit 9

View File

@@ -17,3 +17,5 @@ flag,flag_incorrect_date_format,23,this is set when the given year of birth is i
flag,flag_incorrect_voucher,24,this is set when the selected voucher is invalid flag,flag_incorrect_voucher,24,this is set when the selected voucher is invalid
flag,flag_api_call_error,25,this is set when communication to an external service fails flag,flag_api_call_error,25,this is set when communication to an external service fails
flag,flag_no_active_voucher,26,this is set when a user does not have an active voucher flag,flag_no_active_voucher,26,this is set when a user does not have an active voucher
flag,flag_admin_privilege,27,this is set when a user has admin privileges.
flag,flag_unregistered_number,28,this is set when an unregistered phonenumber tries to perform an action
1 flag flag_language_set 8 checks whether the user has set their prefered language
17 flag flag_incorrect_voucher 24 this is set when the selected voucher is invalid
18 flag flag_api_call_error 25 this is set when communication to an external service fails
19 flag flag_no_active_voucher 26 this is set when a user does not have an active voucher
20 flag flag_admin_privilege 27 this is set when a user has admin privileges.
21 flag flag_unregistered_number 28 this is set when an unregistered phonenumber tries to perform an action

View File

@@ -1,4 +1,4 @@
LOAD save_gender 0 LOAD save_gender 0
CATCH incorrect_pin flag_incorrect_pin 1 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 MOVE pin_entry

View File

@@ -1,4 +1,4 @@
LOAD save_gender 0 LOAD save_gender 0
CATCH incorrect_pin flag_incorrect_pin 1 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 MOVE pin_entry

View File

@@ -1,4 +1,4 @@
LOAD save_gender 0 LOAD save_gender 0
CATCH incorrect_pin flag_incorrect_pin 1 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 MOVE pin_entry

View File

@@ -0,0 +1 @@
The number you have entered is either not registered with Sarafu or is invalid.

View File

@@ -0,0 +1,7 @@
LOAD reset_unregistered_number 0
RELOAD reset_unregistered_number
MOUT back 0
MOUT quit 9
HALT
INCMP ^ 0
INCMP quit 9

View File

@@ -0,0 +1,2 @@
RELOAD save_yob
CATCH profile_update_success flag_allow_update 1

View File

@@ -0,0 +1,2 @@
RELOAD save_familyname
CATCH profile_update_success flag_allow_update 1

View File

@@ -0,0 +1,2 @@
RELOAD save_firstname
CATCH profile_update_success flag_allow_update 1

View File

@@ -0,0 +1,2 @@
RELOAD save_gender
CATCH profile_update_success flag_allow_update 1

View File

@@ -0,0 +1,2 @@
RELOAD save_location
CATCH profile_update_success flag_allow_update 1

View File

@@ -0,0 +1,2 @@
RELOAD save_offerings
CATCH profile_update_success flag_allow_update 1

View File

@@ -0,0 +1,2 @@
RELOAD save_yob
CATCH profile_update_success flag_allow_update 1