Compare commits

..

90 Commits

Author SHA1 Message Date
Carlosokumu
eddadb6491 go to root on retry 2024-12-13 18:02:09 +03:00
Carlosokumu
b014cf0dcc remove all non-incmp statements after HALT 2024-12-13 17:01:01 +03:00
Carlosokumu
a3d63e8bf5 use next node on profile edit 2024-12-13 09:15:01 +03:00
Carlosokumu
2c57535a4e check for back 2024-12-12 15:48:54 +03:00
Carlosokumu
be9d039ef1 update go-vise dep 2024-12-12 15:47:40 +03:00
Carlosokumu
a799e19afc remove check for '0' input 2024-12-12 15:47:07 +03:00
43892f0d8c Merge pull request 'profile-edit-traverse' (#199) from profile-edit-traverse into master
Reviewed-on: urdt/ussd#199
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-12-05 16:37:37 +01:00
Carlosokumu
8e6b1e6f52 Merge branch 'master' into profile-edit-traverse 2024-12-05 18:34:29 +03:00
Carlosokumu
9cbbdff993 chore: use profileDataKeys as an array 2024-12-05 18:25:51 +03:00
316358765d Merge pull request 'data-items-cleanup' (#203) from data-items-cleanup into master
Reviewed-on: urdt/ussd#203
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-12-05 16:03:50 +01:00
alfred-mk
14737b5f12 Removed redundant naming of transfer related data 2024-12-05 17:58:51 +03:00
alfred-mk
caff27b43d Replace IntToBytes(value int) and ToBytes() with a single ToBytes() function 2024-12-05 17:50:40 +03:00
alfred-mk
589a94216b Use the DATATYPE_USERDATA as the prefix for the NewSubPrefixDb func 2024-12-05 17:02:26 +03:00
alfred-mk
a659fb06fa Added a function to convert int to []byte 2024-12-05 16:58:03 +03:00
alfred-mk
f733fe5636 Start the sub prefix data at 256 (0x0100) 2024-12-05 16:31:47 +03:00
alfred-mk
18423fcd9c updated the name of the voucher related data 2024-12-05 16:26:56 +03:00
Carlosokumu
72a3681767 add test data. 2024-12-05 16:03:08 +03:00
Carlosokumu
160ccbb220 read test file from test run arg 2024-12-05 14:05:32 +03:00
Carlosokumu
22f96363ba add test files 2024-12-05 14:04:56 +03:00
Carlosokumu
3e7f90733e update test names 2024-12-05 12:22:29 +03:00
Carlosokumu
321f038c7c iterate over a map for the set profile items 2024-12-05 10:52:45 +03:00
alfred-mk
7a9de79aae return nil with error 2024-12-04 21:07:11 +03:00
alfred-mk
862830e9de renamed internal/storage/db.go -> internal/storage/sub_prefix_db.go for clarity 2024-12-04 20:59:46 +03:00
alfred-mk
bc0e536d3d updated failing tests 2024-12-04 20:55:03 +03:00
alfred-mk
82884a75a3 Merge branch 'master' into data-items-cleanup 2024-12-04 20:45:38 +03:00
alfred-mk
93c44861e0 Use numeric prefixes 2024-12-04 20:42:47 +03:00
alfred-mk
4ecfc9de38 removed DATATYPE_USERSUB and replaced with DATATYPE_USERDATA 2024-12-04 20:38:52 +03:00
Carlosokumu
a84c3e0852 update menu traversal test data 2024-12-04 09:46:16 +03:00
Carlosokumu
8efed966a0 set flag when location is set 2024-12-04 09:08:47 +03:00
Carlosokumu
e7c4b5bca7 update tests 2024-12-04 09:08:25 +03:00
Carlosokumu
ed632248c5 add doc lines and check for back naviagtions 2024-12-04 08:30:07 +03:00
Carlosokumu
5c202741d6 add handler for catching back navigations 2024-12-04 08:20:43 +03:00
Carlosokumu
c5ebdbf85b catch back navigations 2024-12-04 08:20:09 +03:00
Carlosokumu
c4282a870e add flag to catch back navigations 2024-12-04 08:19:20 +03:00
Carlosokumu
91cd6077ce Merge branch 'master' into profile-edit-traverse 2024-12-03 22:25:47 +03:00
Carlosokumu
c0ed6fa9c8 catch incorrect pin entries 2024-12-03 21:43:24 +03:00
22c9c3e0f0 Merge pull request 'minor-bug-fixes' (#177) from minor-bug-fixes into master
Reviewed-on: urdt/ussd#177
2024-12-03 18:18:23 +01:00
a709d24520 Merge branch 'master' into minor-bug-fixes 2024-12-03 18:16:19 +01:00
e56138e416 Merge pull request 'Clear persister from handler in outer code aswell' (#200) from lash/persister-freakout into master
Reviewed-on: urdt/ussd#200
2024-12-03 17:18:11 +01:00
lash
d516584d90 Clear persister from handler in outer code aswell 2024-12-03 16:16:53 +00:00
Carlosokumu
b420a9bba0 set flag if profile data is set 2024-12-03 17:41:06 +03:00
Carlosokumu
a20ab79355 explicit reload save gender 2024-12-03 17:38:20 +03:00
Carlosokumu
e0ec15b272 allow sequential profile edit 2024-12-03 14:40:57 +03:00
Carlosokumu
9e998f9a29 add a zero pad value to unfilled profile item 2024-12-03 14:37:55 +03:00
Carlosokumu
1c7c0af712 catch next unset profile item 2024-12-03 14:36:48 +03:00
alfred-mk
d40a4a171f formatted code 2024-12-03 14:12:47 +03:00
alfred-mk
ba430a5849 add a separate function to handle ConstructName 2024-12-03 14:10:05 +03:00
alfred-mk
0f21b01813 resolved error in the TestViewVoucher 2024-12-03 13:37:00 +03:00
alfred-mk
10586baf0d resolved error in the TestCheckBalance 2024-12-03 13:35:14 +03:00
alfred-mk
e979742424 resolved error in the TestValidateRecipient 2024-12-03 13:32:18 +03:00
alfred-mk
ff3f049226 updated the CheckAliasAddress mock 2024-12-03 13:31:30 +03:00
alfred-mk
13b45c49da Merge branch 'master' into minor-bug-fixes 2024-12-03 12:58:26 +03:00
Carlosokumu
a72fb08dc8 allow sequential profile edit 2024-12-03 11:20:35 +03:00
Carlosokumu
944fa89b3c add profile holder struct 2024-12-03 11:19:38 +03:00
Carlosokumu
48e1b02e0e allow all item profile edit 2024-12-02 20:50:21 +03:00
Carlosokumu
3e0bbe5ffe add handler to update profile items 2024-12-02 20:35:56 +03:00
Carlosokumu
fd586d09c3 add required profile edit flags 2024-12-02 20:35:04 +03:00
b615c27cf6 Merge pull request 'trigger-balance-reload' (#193) from trigger-balance-reload into master
Reviewed-on: urdt/ussd#193
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-12-02 16:36:26 +01:00
22e870b3e5 Merge branch 'master' into trigger-balance-reload 2024-12-02 16:36:08 +01:00
da462346f9 Merge pull request 'Always reset persister in handler' (#194) from lash/no-persister-deadlock into master
Reviewed-on: urdt/ussd#194
2024-12-02 16:17:36 +01:00
lash
406bd84875 Always reset persister in handler 2024-12-02 14:53:18 +00:00
alfred-mk
419cd185fc Merge branch 'master' into minor-bug-fixes 2024-12-02 15:25:34 +03:00
7976e237ca Merge pull request 'voucher-details' (#179) from voucher-details into master
Reviewed-on: urdt/ussd#179
Reviewed-by: Alfred Kamanda <alfredkamandamw@gmail.com>
2024-12-02 11:43:30 +01:00
alfred-mk
19ec8f0817 Merge branch 'master' into voucher-details 2024-12-02 13:32:32 +03:00
alfred-mk
ef3a3d6717 updated the TestGetVoucherDetails 2024-12-02 13:30:33 +03:00
alfred-mk
c2019267d1 capitalize the voucher descriptions 2024-12-02 13:26:46 +03:00
Carlosokumu
0091fbcabb fix: looped navigation 2024-12-02 09:03:04 +03:00
alfred-mk
aa7497573e removed unused code 2024-11-30 15:29:28 +03:00
alfred-mk
54c1fe51ef update the active voucher data when checking the current vouchers 2024-11-30 15:28:21 +03:00
alfred-mk
7a86b2ad3b updated the UpdateVoucherData description 2024-11-30 15:26:13 +03:00
alfred-mk
6b23c284e5 check vouchers before checking the balance 2024-11-30 15:24:14 +03:00
alfred-mk
aab6660edd Capitalize menu items 2024-11-29 15:39:27 +03:00
alfred-mk
c46f41e25f Format the balance to 2 decimal places 2024-11-29 14:47:22 +03:00
Carlosokumu
00c0445eed show name without depending on family name being set 2024-11-28 11:42:47 +03:00
alfred-mk
c8c6b05b8a Merge branch 'master' into minor-bug-fixes 2024-11-26 15:31:45 +03:00
Carlosokumu
a17cf78d29 Merge remote-tracking branch 'refs/remotes/origin/minor-bug-fixes' into minor-bug-fixes 2024-11-22 11:35:57 +03:00
Carlosokumu
9847433e0a use _ for back navigation 2024-11-22 11:30:13 +03:00
alfred-mk
7ce50398d1 use the language translation instead of hardcoded eng 2024-11-21 15:54:00 +03:00
alfred-mk
e30bc177e9 fixed typo and added a new translation 2024-11-21 15:52:07 +03:00
alfred-mk
b9ff467c0c use the correct balance 2024-11-21 15:51:04 +03:00
Carlosokumu
1174500e3f add test for voucher details 2024-11-21 15:19:36 +03:00
alfred-mk
07df450b3c include labels to define the symbol and balance while selecting a voucher 2024-11-21 15:15:15 +03:00
Carlosokumu
b8d938d3aa add voucher details 2024-11-21 13:04:19 +03:00
Carlosokumu
d1e9340ea9 add voucher details 2024-11-21 13:03:43 +03:00
Carlosokumu
8925e26c4c refactor check for valid yob 2024-11-21 11:25:47 +03:00
Carlosokumu
9b89462797 add function to check validity of provided yob 2024-11-21 11:21:16 +03:00
Carlosokumu
7880294c6f set eng as default language 2024-11-20 17:14:25 +03:00
Carlosokumu
451b15fb6b explicit set_language reload 2024-11-20 17:13:14 +03:00
Carlosokumu
d20700ca74 fix size limit error 2024-11-20 16:39:50 +03:00
Carlosokumu
9cf1cbe425 add swahili translation for catch node 2024-11-20 16:36:18 +03:00
66 changed files with 1038 additions and 250 deletions

View File

@@ -32,7 +32,19 @@ const (
DATA_PUBLIC_KEY_REVERSE DATA_PUBLIC_KEY_REVERSE
DATA_ACTIVE_DECIMAL DATA_ACTIVE_DECIMAL
DATA_ACTIVE_ADDRESS DATA_ACTIVE_ADDRESS
DATA_TRANSACTIONS // Start the sub prefix data at 256 (0x0100)
DATA_VOUCHER_SYMBOLS DataTyp = 256 + iota
DATA_VOUCHER_BALANCES
DATA_VOUCHER_DECIMALS
DATA_VOUCHER_ADDRESSES
DATA_TX_SENDERS
DATA_TX_RECIPIENTS
DATA_TX_VALUES
DATA_TX_ADDRESSES
DATA_TX_HASHES
DATA_TX_DATES
DATA_TX_SYMBOLS
DATA_TX_DECIMALS
) )
var ( var (
@@ -69,3 +81,10 @@ func StringToDataTyp(str string) (DataTyp, error) {
return 0, errors.New("invalid DataTyp string") return 0, errors.New("invalid DataTyp string")
} }
} }
// ToBytes converts DataTyp or int to a byte slice
func ToBytes[T ~uint16 | int](value T) []byte {
bytes := make([]byte, 2)
binary.BigEndian.PutUint16(bytes, uint16(value))
return bytes
}

View File

@@ -57,25 +57,25 @@ func ProcessTransfers(transfers []dataserviceapi.Last10TxResponse) TransferMetad
// GetTransferData retrieves and matches transfer data // GetTransferData retrieves and matches transfer data
// returns a formatted string of the full transaction/statement // returns a formatted string of the full transaction/statement
func GetTransferData(ctx context.Context, db storage.PrefixDb, publicKey string, index int) (string, error) { func GetTransferData(ctx context.Context, db storage.PrefixDb, publicKey string, index int) (string, error) {
keys := []string{"txfrom", "txto", "txval", "txaddr", "txhash", "txdate", "txsym"} keys := []DataTyp{DATA_TX_SENDERS, DATA_TX_RECIPIENTS, DATA_TX_VALUES, DATA_TX_ADDRESSES, DATA_TX_HASHES, DATA_TX_DATES, DATA_TX_SYMBOLS}
data := make(map[string]string) data := make(map[DataTyp]string)
for _, key := range keys { for _, key := range keys {
value, err := db.Get(ctx, []byte(key)) value, err := db.Get(ctx, ToBytes(key))
if err != nil { if err != nil {
return "", fmt.Errorf("failed to get %s: %v", key, err) return "", fmt.Errorf("failed to get %s: %v", ToBytes(key), err)
} }
data[key] = string(value) data[key] = string(value)
} }
// Split the data // Split the data
senders := strings.Split(string(data["txfrom"]), "\n") senders := strings.Split(string(data[DATA_TX_SENDERS]), "\n")
recipients := strings.Split(string(data["txto"]), "\n") recipients := strings.Split(string(data[DATA_TX_RECIPIENTS]), "\n")
values := strings.Split(string(data["txval"]), "\n") values := strings.Split(string(data[DATA_TX_VALUES]), "\n")
addresses := strings.Split(string(data["txaddr"]), "\n") addresses := strings.Split(string(data[DATA_TX_ADDRESSES]), "\n")
hashes := strings.Split(string(data["txhash"]), "\n") hashes := strings.Split(string(data[DATA_TX_HASHES]), "\n")
dates := strings.Split(string(data["txdate"]), "\n") dates := strings.Split(string(data[DATA_TX_DATES]), "\n")
syms := strings.Split(string(data["txsym"]), "\n") syms := strings.Split(string(data[DATA_TX_SYMBOLS]), "\n")
// Check if index is within range // Check if index is within range
if index < 1 || index > len(senders) { if index < 1 || index > len(senders) {

View File

@@ -64,22 +64,23 @@ func ScaleDownBalance(balance, decimals string) string {
// GetVoucherData retrieves and matches voucher data // GetVoucherData retrieves and matches voucher data
func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) { func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) {
keys := []string{"sym", "bal", "deci", "addr"} keys := []DataTyp{DATA_VOUCHER_SYMBOLS, DATA_VOUCHER_BALANCES, DATA_VOUCHER_DECIMALS, DATA_VOUCHER_ADDRESSES}
data := make(map[string]string) data := make(map[DataTyp]string)
for _, key := range keys { for _, key := range keys {
value, err := db.Get(ctx, []byte(key)) value, err := db.Get(ctx, ToBytes(key))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get %s: %v", key, err) return nil, fmt.Errorf("failed to get %s: %v", ToBytes(key), err)
} }
data[key] = string(value) data[key] = string(value)
} }
symbol, balance, decimal, address := MatchVoucher(input, symbol, balance, decimal, address := MatchVoucher(input,
data["sym"], data[DATA_VOUCHER_SYMBOLS],
data["bal"], data[DATA_VOUCHER_BALANCES],
data["deci"], data[DATA_VOUCHER_DECIMALS],
data["addr"]) data[DATA_VOUCHER_ADDRESSES],
)
if symbol == "" { if symbol == "" {
return nil, nil return nil, nil
@@ -151,7 +152,7 @@ func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId str
return data, nil return data, nil
} }
// UpdateVoucherData sets the active voucher data in the DataStore. // UpdateVoucherData updates the active voucher data in the DataStore.
func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error { func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
logg.TraceCtxf(ctx, "dtal", "data", data) logg.TraceCtxf(ctx, "dtal", "data", data)
// Active voucher data entries // Active voucher data entries

View File

@@ -8,8 +8,9 @@ import (
"github.com/alecthomas/assert/v2" "github.com/alecthomas/assert/v2"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"git.grassecon.net/urdt/ussd/internal/storage" visedb "git.defalsify.org/vise.git/db"
memdb "git.defalsify.org/vise.git/db/mem" memdb "git.defalsify.org/vise.git/db/mem"
"git.grassecon.net/urdt/ussd/internal/storage"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
@@ -83,19 +84,21 @@ func TestGetVoucherData(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
spdb := storage.NewSubPrefixDb(db, []byte("vouchers"))
prefix := ToBytes(visedb.DATATYPE_USERDATA)
spdb := storage.NewSubPrefixDb(db, prefix)
// Test voucher data // Test voucher data
mockData := map[string][]byte{ mockData := map[DataTyp][]byte{
"sym": []byte("1:SRF\n2:MILO"), DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"),
"bal": []byte("1:100\n2:200"), DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"),
"deci": []byte("1:6\n2:4"), DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"),
"addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
} }
// Put the data // Put the data
for key, value := range mockData { for key, value := range mockData {
err = spdb.Put(ctx, []byte(key), []byte(value)) err = spdb.Put(ctx, []byte(ToBytes(key)), []byte(value))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

2
go.mod
View File

@@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd
go 1.23.0 go 1.23.0
require ( require (
git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a git.defalsify.org/vise.git v0.2.1-0.20241209194740-26fc82a76ede
github.com/alecthomas/assert/v2 v2.2.2 github.com/alecthomas/assert/v2 v2.2.2
github.com/gofrs/uuid v4.4.0+incompatible github.com/gofrs/uuid v4.4.0+incompatible
github.com/grassrootseconomics/eth-custodial v1.3.0-beta github.com/grassrootseconomics/eth-custodial v1.3.0-beta

2
go.sum
View File

@@ -1,5 +1,7 @@
git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a h1:LvGKktk0kUnuRN3nF9r15D8OoV0sFaMmQr52kGq2gtE= git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a h1:LvGKktk0kUnuRN3nF9r15D8OoV0sFaMmQr52kGq2gtE=
git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
git.defalsify.org/vise.git v0.2.1-0.20241209194740-26fc82a76ede h1:BtooQZJDYEgeRKqv8RSxYSbW5jh0UIFJ6zRMEZZn0sc=
git.defalsify.org/vise.git v0.2.1-0.20241209194740-26fc82a76ede/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=

View File

@@ -55,6 +55,9 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error)
} }
f.hn = f.hn.WithPersister(rqs.Storage.Persister) f.hn = f.hn.WithPersister(rqs.Storage.Persister)
defer func() {
f.hn.Exit()
}()
eni := f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister) eni := f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister)
en, ok := eni.(*engine.DefaultEngine) en, ok := eni.(*engine.DefaultEngine)
if !ok { if !ok {

View File

@@ -121,6 +121,8 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn
ls.DbRs.AddLocalFunc("check_transactions", ussdHandlers.CheckTransactions) ls.DbRs.AddLocalFunc("check_transactions", ussdHandlers.CheckTransactions)
ls.DbRs.AddLocalFunc("get_transactions", ussdHandlers.GetTransactionsList) ls.DbRs.AddLocalFunc("get_transactions", ussdHandlers.GetTransactionsList)
ls.DbRs.AddLocalFunc("view_statement", ussdHandlers.ViewTransactionStatement) ls.DbRs.AddLocalFunc("view_statement", ussdHandlers.ViewTransactionStatement)
ls.DbRs.AddLocalFunc("update_all_profile_items", ussdHandlers.UpdateAllProfileItems)
ls.DbRs.AddLocalFunc("set_back", ussdHandlers.SetBack)
return ussdHandlers, nil return ussdHandlers, nil
} }

View File

@@ -10,7 +10,6 @@ import (
"strings" "strings"
"git.defalsify.org/vise.git/asm" "git.defalsify.org/vise.git/asm"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
"git.defalsify.org/vise.git/cache" "git.defalsify.org/vise.git/cache"
"git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/db"
@@ -21,18 +20,18 @@ import (
"git.defalsify.org/vise.git/state" "git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/common" "git.grassecon.net/urdt/ussd/common"
"git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/internal/utils"
"git.grassecon.net/urdt/ussd/models"
"git.grassecon.net/urdt/ussd/remote" "git.grassecon.net/urdt/ussd/remote"
"gopkg.in/leonelquinteros/gotext.v1" "gopkg.in/leonelquinteros/gotext.v1"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
var ( var (
logg = logging.NewVanilla().WithDomain("ussdmenuhandler") logg = logging.NewVanilla().WithDomain("ussdmenuhandler")
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
translationDir = path.Join(scriptDir, "locale") translationDir = path.Join(scriptDir, "locale")
okResponse *api.OKResponse
errResponse *api.ErrResponse
) )
// Define the regex patterns as constants // Define the regex patterns as constants
@@ -78,6 +77,7 @@ type Handlers struct {
flagManager *asm.FlagParser flagManager *asm.FlagParser
accountService remote.AccountServiceInterface accountService remote.AccountServiceInterface
prefixDb storage.PrefixDb prefixDb storage.PrefixDb
profile *models.Profile
} }
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface) (*Handlers, error) { func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface) (*Handlers, error) {
@@ -87,8 +87,10 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util
userDb := &common.UserDataStore{ userDb := &common.UserDataStore{
Db: userdataStore, Db: userdataStore,
} }
// Instantiate the SubPrefixDb with "vouchers" prefix
prefixDb := storage.NewSubPrefixDb(userdataStore, []byte("vouchers")) // Instantiate the SubPrefixDb with "DATATYPE_USERDATA" prefix
prefix := common.ToBytes(db.DATATYPE_USERDATA)
prefixDb := storage.NewSubPrefixDb(userdataStore, prefix)
h := &Handlers{ h := &Handlers{
userdataStore: userDb, userdataStore: userDb,
@@ -96,6 +98,7 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util
adminstore: adminstore, adminstore: adminstore,
accountService: accountService, accountService: accountService,
prefixDb: prefixDb, prefixDb: prefixDb,
profile: &models.Profile{Max: 6},
} }
return h, nil return h, nil
} }
@@ -114,6 +117,9 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource
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
} }
defer func() {
h.Exit()
}()
h.st = h.pe.GetState() h.st = h.pe.GetState()
h.ca = h.pe.GetMemory() h.ca = h.pe.GetMemory()
@@ -133,13 +139,16 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource
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")
} }
h.pe = nil
logg.DebugCtxf(ctx, "handler has been initialized", "state", h.st, "cache", h.ca) logg.DebugCtxf(ctx, "handler has been initialized", "state", h.st, "cache", h.ca)
return r, nil return r, nil
} }
func (h *Handlers) Exit() {
h.pe = nil
}
// SetLanguage sets the language across the menu // SetLanguage sets the language across the menu
func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@@ -148,7 +157,8 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r
code := strings.Split(symbol, "_")[1] code := strings.Split(symbol, "_")[1]
if !utils.IsValidISO639(code) { if !utils.IsValidISO639(code) {
return res, nil //Fallback to english instead?
code = "eng"
} }
res.FlagSet = append(res.FlagSet, state.FLAG_LANG) res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
res.Content = code res.Content = code
@@ -406,7 +416,10 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
firstName := string(input) firstName := string(input)
store := h.userdataStore store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
firstNameSet := h.st.MatchFlag(flag_firstname_set, true)
if allowUpdate { if allowUpdate {
temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, common.DATA_FIRST_NAME, []byte(temporaryFirstName)) err = store.WriteEntry(ctx, sessionId, common.DATA_FIRST_NAME, []byte(temporaryFirstName))
@@ -414,11 +427,16 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
logg.ErrorCtxf(ctx, "failed to write firstName entry with", "key", common.DATA_FIRST_NAME, "value", temporaryFirstName, "error", err) logg.ErrorCtxf(ctx, "failed to write firstName entry with", "key", common.DATA_FIRST_NAME, "value", temporaryFirstName, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_firstname_set)
} else { } else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName)) if firstNameSet {
if err != nil { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName))
logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", firstName, "error", err) if err != nil {
return res, err logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", firstName, "error", err)
return res, err
}
} else {
h.profile.InsertOrShift(0, firstName)
} }
} }
@@ -438,7 +456,9 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
familyName := string(input) familyName := string(input)
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
familyNameSet := h.st.MatchFlag(flag_familyname_set, true)
if allowUpdate { if allowUpdate {
temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@@ -447,11 +467,16 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
logg.ErrorCtxf(ctx, "failed to write familyName entry with", "key", common.DATA_FAMILY_NAME, "value", temporaryFamilyName, "error", err) logg.ErrorCtxf(ctx, "failed to write familyName entry with", "key", common.DATA_FAMILY_NAME, "value", temporaryFamilyName, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_familyname_set)
} else { } else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName)) if familyNameSet {
if err != nil { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName))
logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", familyName, "error", err) if err != nil {
return res, err logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", familyName, "error", err)
return res, err
}
} else {
h.profile.InsertOrShift(1, familyName)
} }
} }
@@ -469,7 +494,10 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
yob := string(input) yob := string(input)
store := h.userdataStore store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
yobSet := h.st.MatchFlag(flag_yob_set, true)
if allowUpdate { if allowUpdate {
temporaryYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@@ -478,11 +506,16 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
logg.ErrorCtxf(ctx, "failed to write yob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", temporaryYob, "error", err) logg.ErrorCtxf(ctx, "failed to write yob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", temporaryYob, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_yob_set)
} else { } else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob)) if yobSet {
if err != nil { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob))
logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", yob, "error", err) if err != nil {
return res, err logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", yob, "error", err)
return res, err
}
} else {
h.profile.InsertOrShift(3, yob)
} }
} }
@@ -501,7 +534,9 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
store := h.userdataStore store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_location_set, _ := h.flagManager.GetFlag("flag_location_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
locationSet := h.st.MatchFlag(flag_location_set, true)
if allowUpdate { if allowUpdate {
temporaryLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@@ -510,11 +545,17 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
logg.ErrorCtxf(ctx, "failed to write location entry with", "key", common.DATA_LOCATION, "value", temporaryLocation, "error", err) logg.ErrorCtxf(ctx, "failed to write location entry with", "key", common.DATA_LOCATION, "value", temporaryLocation, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_location_set)
} else { } else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location)) if locationSet {
if err != nil { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location))
logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", common.DATA_TEMPORARY_VALUE, "value", location, "error", err) if err != nil {
return res, err logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", common.DATA_TEMPORARY_VALUE, "value", location, "error", err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_location_set)
} else {
h.profile.InsertOrShift(4, location)
} }
} }
@@ -533,7 +574,10 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
gender := strings.Split(symbol, "_")[1] gender := strings.Split(symbol, "_")[1]
store := h.userdataStore store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
genderSet := h.st.MatchFlag(flag_gender_set, true)
if allowUpdate { if allowUpdate {
temporaryGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@@ -542,11 +586,16 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
logg.ErrorCtxf(ctx, "failed to write gender entry with", "key", common.DATA_GENDER, "value", gender, "error", err) logg.ErrorCtxf(ctx, "failed to write gender entry with", "key", common.DATA_GENDER, "value", gender, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_gender_set)
} else { } else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(gender)) if genderSet {
if err != nil { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(gender))
logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", common.DATA_TEMPORARY_VALUE, "value", gender, "error", err) if err != nil {
return res, err logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", common.DATA_TEMPORARY_VALUE, "value", gender, "error", err)
return res, err
}
} else {
h.profile.InsertOrShift(2, gender)
} }
} }
@@ -566,7 +615,10 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
store := h.userdataStore store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
offeringsSet := h.st.MatchFlag(flag_offerings_set, true)
if allowUpdate { if allowUpdate {
temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@@ -575,11 +627,16 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
logg.ErrorCtxf(ctx, "failed to write offerings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err) logg.ErrorCtxf(ctx, "failed to write offerings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_offerings_set)
} else { } else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings)) if offeringsSet {
if err != nil { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings))
logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err) if err != nil {
return res, err logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
return res, err
}
} else {
h.profile.InsertOrShift(5, offerings)
} }
} }
@@ -672,6 +729,16 @@ func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []by
return res, nil return res, nil
} }
// Setback sets the flag_back_set flag when the navigation is back
func (h *Handlers) SetBack(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
if h.st.Back() {
flag_back_set, _ := h.flagManager.GetFlag("flag_back_set")
res.FlagSet = append(res.FlagSet, flag_back_set)
}
return res, nil
}
// CheckAccountStatus queries the API using the TrackingId and sets flags // CheckAccountStatus queries the API using the TrackingId and sets flags
// based on the account status // based on the account status
func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
@@ -757,12 +824,11 @@ func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (res
return res, nil return res, nil
} }
if len(date) == 4 { if utils.IsValidYOb(date) {
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
} else { } else {
res.FlagSet = append(res.FlagSet, flag_incorrect_date_format) res.FlagSet = append(res.FlagSet, flag_incorrect_date_format)
} }
return res, nil return res, nil
} }
@@ -811,7 +877,17 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
return res, err return res, err
} }
res.Content = l.Get("Balance: %s\n", fmt.Sprintf("%s %s", activeBal, activeSym)) // Convert activeBal from []byte to float64
balFloat, err := strconv.ParseFloat(string(activeBal), 64)
if err != nil {
logg.ErrorCtxf(ctx, "failed to parse activeBal as float", "value", string(activeBal), "error", err)
return res, err
}
// Format to 2 decimal places
balStr := fmt.Sprintf("%.2f %s", balFloat, activeSym)
res.Content = l.Get("Balance: %s\n", balStr)
return res, nil return res, nil
} }
@@ -1262,6 +1338,17 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
var res resource.Result var res resource.Result
var profileInfo []byte var profileInfo []byte
var err error var err error
flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set")
flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set")
flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set")
flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set")
flag_location_set, _ := h.flagManager.GetFlag("flag_location_set")
flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set")
flag_back_set, _ := h.flagManager.GetFlag("flag_back_set")
res.FlagReset = append(res.FlagReset, flag_back_set)
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")
@@ -1288,6 +1375,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read first name entry with", "key", "error", common.DATA_FIRST_NAME, err) logg.ErrorCtxf(ctx, "Failed to read first name entry with", "key", "error", common.DATA_FIRST_NAME, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_firstname_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
case common.DATA_FAMILY_NAME: case common.DATA_FAMILY_NAME:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME) profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME)
@@ -1299,6 +1387,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read family name entry with", "key", "error", common.DATA_FAMILY_NAME, err) logg.ErrorCtxf(ctx, "Failed to read family name entry with", "key", "error", common.DATA_FAMILY_NAME, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_familyname_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
case common.DATA_GENDER: case common.DATA_GENDER:
@@ -1311,6 +1400,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read gender entry with", "key", "error", common.DATA_GENDER, err) logg.ErrorCtxf(ctx, "Failed to read gender entry with", "key", "error", common.DATA_GENDER, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_gender_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
case common.DATA_YOB: case common.DATA_YOB:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_YOB) profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_YOB)
@@ -1322,8 +1412,8 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read year of birth(yob) entry with", "key", "error", common.DATA_YOB, err) logg.ErrorCtxf(ctx, "Failed to read year of birth(yob) entry with", "key", "error", common.DATA_YOB, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_yob_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
case common.DATA_LOCATION: case common.DATA_LOCATION:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_LOCATION) profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_LOCATION)
if err != nil { if err != nil {
@@ -1334,6 +1424,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read location entry with", "key", "error", common.DATA_LOCATION, err) logg.ErrorCtxf(ctx, "Failed to read location entry with", "key", "error", common.DATA_LOCATION, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_location_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
case common.DATA_OFFERINGS: case common.DATA_OFFERINGS:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS) profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)
@@ -1345,6 +1436,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read offerings entry with", "key", "error", common.DATA_OFFERINGS, err) logg.ErrorCtxf(ctx, "Failed to read offerings entry with", "key", "error", common.DATA_OFFERINGS, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_offerings_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
default: default:
break break
@@ -1388,14 +1480,7 @@ func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte)
offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)) offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS))
// Construct the full name // Construct the full name
name := defaultValue name := utils.ConstructName(firstName, familyName, defaultValue)
if familyName != defaultValue {
if firstName == defaultValue {
name = familyName
} else {
name = firstName + " " + familyName
}
}
// Calculate age from year of birth // Calculate age from year of birth
age := defaultValue age := defaultValue
@@ -1534,18 +1619,50 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
return res, nil return res, nil
} }
// check the current active sym and update the data
activeSym, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
if activeSym != nil {
activeSymStr := string(activeSym)
// Find the matching voucher data
var activeData *dataserviceapi.TokenHoldings
for _, voucher := range vouchersResp {
if voucher.TokenSymbol == activeSymStr {
activeData = &voucher
break
}
}
if activeData == nil {
logg.ErrorCtxf(ctx, "activeSym not found in vouchers", "activeSym", activeSymStr)
return res, fmt.Errorf("activeSym %s not found in vouchers", activeSymStr)
}
// Scale down the balance
scaledBalance := common.ScaleDownBalance(activeData.Balance, activeData.TokenDecimals)
// Update the balance field with the scaled value
activeData.Balance = scaledBalance
// Pass the matching voucher data to UpdateVoucherData
if err := common.UpdateVoucherData(ctx, h.userdataStore, sessionId, activeData); err != nil {
logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err)
return res, err
}
}
data := common.ProcessVouchers(vouchersResp) data := common.ProcessVouchers(vouchersResp)
// Store all voucher data // Store all voucher data
dataMap := map[string]string{ dataMap := map[common.DataTyp]string{
"sym": data.Symbols, common.DATA_VOUCHER_SYMBOLS: data.Symbols,
"bal": data.Balances, common.DATA_VOUCHER_BALANCES: data.Balances,
"deci": data.Decimals, common.DATA_VOUCHER_DECIMALS: data.Decimals,
"addr": data.Addresses, common.DATA_VOUCHER_ADDRESSES: data.Addresses,
} }
for key, value := range dataMap { for key, value := range dataMap {
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil { if err := h.prefixDb.Put(ctx, []byte(common.ToBytes(key)), []byte(value)); err != nil {
return res, nil return res, nil
} }
} }
@@ -1558,7 +1675,7 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte)
var res resource.Result var res resource.Result
// Read vouchers from the store // Read vouchers from the store
voucherData, err := h.prefixDb.Get(ctx, []byte("sym")) voucherData, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS))
if err != nil { if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the voucherData from prefixDb", "error", err) logg.ErrorCtxf(ctx, "Failed to read the voucherData from prefixDb", "error", err)
return res, err return res, err
@@ -1578,6 +1695,10 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
code := codeFromCtx(ctx)
l := gotext.NewLocale(translationDir, code)
l.AddDomain("default")
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher") flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
inputStr := string(input) inputStr := string(input)
@@ -1602,7 +1723,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
} }
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
res.Content = fmt.Sprintf("%s\n%s", metadata.TokenSymbol, metadata.Balance) res.Content = l.Get("Symbol: %s\nBalance: %s", metadata.TokenSymbol, metadata.Balance)
return res, nil return res, nil
} }
@@ -1658,10 +1779,9 @@ func (h *Handlers) GetVoucherDetails(ctx context.Context, sym string, input []by
return res, nil return res, nil
} }
tokenSymbol := voucherData.TokenSymbol res.Content = fmt.Sprintf(
tokenName := voucherData.TokenName "Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", voucherData.TokenName, voucherData.TokenSymbol, voucherData.TokenCommodity, voucherData.TokenLocation,
)
res.Content = fmt.Sprintf("%s %s", tokenSymbol, tokenName)
return res, nil return res, nil
} }
@@ -1701,19 +1821,19 @@ func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []by
data := common.ProcessTransfers(transactionsResp) data := common.ProcessTransfers(transactionsResp)
// Store all transaction data // Store all transaction data
dataMap := map[string]string{ dataMap := map[common.DataTyp]string{
"txfrom": data.Senders, common.DATA_TX_SENDERS: data.Senders,
"txto": data.Recipients, common.DATA_TX_RECIPIENTS: data.Recipients,
"txval": data.TransferValues, common.DATA_TX_VALUES: data.TransferValues,
"txaddr": data.Addresses, common.DATA_TX_ADDRESSES: data.Addresses,
"txhash": data.TxHashes, common.DATA_TX_HASHES: data.TxHashes,
"txdate": data.Dates, common.DATA_TX_DATES: data.Dates,
"txsym": data.Symbols, common.DATA_TX_SYMBOLS: data.Symbols,
"txdeci": data.Decimals, common.DATA_TX_DECIMALS: data.Decimals,
} }
for key, value := range dataMap { for key, value := range dataMap {
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil { if err := h.prefixDb.Put(ctx, []byte(common.ToBytes(key)), []byte(value)); err != nil {
logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err) logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err)
return res, err return res, err
} }
@@ -1739,22 +1859,22 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input []
} }
// Read transactions from the store and format them // Read transactions from the store and format them
TransactionSenders, err := h.prefixDb.Get(ctx, []byte("txfrom")) TransactionSenders, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_SENDERS))
if err != nil { if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionSenders from prefixDb", "error", err) logg.ErrorCtxf(ctx, "Failed to read the TransactionSenders from prefixDb", "error", err)
return res, err return res, err
} }
TransactionSyms, err := h.prefixDb.Get(ctx, []byte("txsym")) TransactionSyms, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_SYMBOLS))
if err != nil { if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionSyms from prefixDb", "error", err) logg.ErrorCtxf(ctx, "Failed to read the TransactionSyms from prefixDb", "error", err)
return res, err return res, err
} }
TransactionValues, err := h.prefixDb.Get(ctx, []byte("txval")) TransactionValues, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_VALUES))
if err != nil { if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionValues from prefixDb", "error", err) logg.ErrorCtxf(ctx, "Failed to read the TransactionValues from prefixDb", "error", err)
return res, err return res, err
} }
TransactionDates, err := h.prefixDb.Get(ctx, []byte("txdate")) TransactionDates, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_DATES))
if err != nil { if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err) logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err)
return res, err return res, err
@@ -1834,3 +1954,53 @@ func (h *Handlers) ViewTransactionStatement(ctx context.Context, sym string, inp
return res, nil return res, nil
} }
func (h *Handlers) insertProfileItems(ctx context.Context, sessionId string, res *resource.Result) error {
var err error
store := h.userdataStore
profileFlagNames := []string{
"flag_firstname_set",
"flag_familyname_set",
"flag_yob_set",
"flag_gender_set",
"flag_location_set",
"flag_offerings_set",
}
profileDataKeys := []common.DataTyp{
common.DATA_FIRST_NAME,
common.DATA_FAMILY_NAME,
common.DATA_GENDER,
common.DATA_YOB,
common.DATA_LOCATION,
common.DATA_OFFERINGS,
}
for index, profileItem := range h.profile.ProfileItems {
// Ensure the profileItem is not "0"(is set)
if profileItem != "0" {
err = store.WriteEntry(ctx, sessionId, profileDataKeys[index], []byte(profileItem))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write profile entry with", "key", profileDataKeys[index], "value", profileItem, "error", err)
return err
}
// Get the flag for the current index
flag, _ := h.flagManager.GetFlag(profileFlagNames[index])
res.FlagSet = append(res.FlagSet, flag)
}
}
return nil
}
// UpdateAllProfileItems is used to persist all the new profile information and setup the required profile flags
func (h *Handlers) UpdateAllProfileItems(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")
}
err := h.insertProfileItems(ctx, sessionId, &res)
if err != nil {
return res, err
}
return res, nil
}

View File

@@ -22,6 +22,7 @@ import (
testdataloader "github.com/peteole/testdata-loader" testdataloader "github.com/peteole/testdata-loader"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
visedb "git.defalsify.org/vise.git/db"
memdb "git.defalsify.org/vise.git/db/mem" memdb "git.defalsify.org/vise.git/db/mem"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
@@ -56,7 +57,8 @@ func InitializeTestSubPrefixDb(t *testing.T, ctx context.Context) *storage.SubPr
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
spdb := storage.NewSubPrefixDb(db, []byte("vouchers")) prefix := common.ToBytes(visedb.DATATYPE_USERDATA)
spdb := storage.NewSubPrefixDb(db, prefix)
return spdb return spdb
} }
@@ -179,11 +181,14 @@ func TestSaveFirstname(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_firstname_set, _ := fm.GetFlag("flag_firstname_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(16) mockState := state.NewState(128)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data // Define test data
firstName := "John" firstName := "John"
@@ -191,6 +196,8 @@ func TestSaveFirstname(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expectedResult.FlagSet = []uint32{flag_firstname_set}
// Create the Handlers instance with the mock store // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: store, userdataStore: store,
@@ -203,7 +210,7 @@ func TestSaveFirstname(t *testing.T) {
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, resource.Result{}, res) assert.Equal(t, expectedResult, res)
// Verify that the DATA_FIRST_NAME entry has been updated with the temporary value // Verify that the DATA_FIRST_NAME entry has been updated with the temporary value
storedFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME) storedFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME)
@@ -218,11 +225,16 @@ func TestSaveFamilyname(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_firstname_set, _ := fm.GetFlag("flag_familyname_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(16) mockState := state.NewState(128)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
expectedResult.FlagSet = []uint32{flag_firstname_set}
// Define test data // Define test data
familyName := "Doeee" familyName := "Doeee"
@@ -242,7 +254,7 @@ func TestSaveFamilyname(t *testing.T) {
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, resource.Result{}, res) assert.Equal(t, expectedResult, res)
// Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value // Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value
storedFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME) storedFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME)
@@ -257,11 +269,14 @@ func TestSaveYoB(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_yob_set, _ := fm.GetFlag("flag_yob_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(16) mockState := state.NewState(108)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data // Define test data
yob := "1980" yob := "1980"
@@ -269,6 +284,8 @@ func TestSaveYoB(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expectedResult.FlagSet = []uint32{flag_yob_set}
// Create the Handlers instance with the mock store // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: store, userdataStore: store,
@@ -281,7 +298,7 @@ func TestSaveYoB(t *testing.T) {
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, resource.Result{}, res) assert.Equal(t, expectedResult, res)
// Verify that the DATA_YOB entry has been updated with the temporary value // Verify that the DATA_YOB entry has been updated with the temporary value
storedYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_YOB) storedYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_YOB)
@@ -296,11 +313,14 @@ func TestSaveLocation(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_location_set, _ := fm.GetFlag("flag_location_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(16) mockState := state.NewState(108)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data // Define test data
location := "Kilifi" location := "Kilifi"
@@ -308,6 +328,8 @@ func TestSaveLocation(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expectedResult.FlagSet = []uint32{flag_location_set}
// Create the Handlers instance with the mock store // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: store, userdataStore: store,
@@ -320,7 +342,7 @@ func TestSaveLocation(t *testing.T) {
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, resource.Result{}, res) assert.Equal(t, expectedResult, res)
// Verify that the DATA_LOCATION entry has been updated with the temporary value // Verify that the DATA_LOCATION entry has been updated with the temporary value
storedLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_LOCATION) storedLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_LOCATION)
@@ -335,11 +357,14 @@ func TestSaveOfferings(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_offerings_set, _ := fm.GetFlag("flag_offerings_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(16) mockState := state.NewState(108)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data // Define test data
offerings := "Bananas" offerings := "Bananas"
@@ -347,6 +372,8 @@ func TestSaveOfferings(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expectedResult.FlagSet = []uint32{flag_offerings_set}
// Create the Handlers instance with the mock store // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: store, userdataStore: store,
@@ -359,7 +386,7 @@ func TestSaveOfferings(t *testing.T) {
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, resource.Result{}, res) assert.Equal(t, expectedResult, res)
// Verify that the DATA_OFFERINGS entry has been updated with the temporary value // Verify that the DATA_OFFERINGS entry has been updated with the temporary value
storedOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS) storedOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)
@@ -374,9 +401,10 @@ func TestSaveGender(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_gender_set, _ := fm.GetFlag("flag_gender_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(16) mockState := state.NewState(108)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
// Define test cases // Define test cases
@@ -420,12 +448,16 @@ func TestSaveGender(t *testing.T) {
flagManager: fm.parser, flagManager: fm.parser,
} }
expectedResult := resource.Result{}
// Call the method // Call the method
res, err := h.SaveGender(ctx, "save_gender", tt.input) res, err := h.SaveGender(ctx, "save_gender", tt.input)
expectedResult.FlagSet = []uint32{flag_gender_set}
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, resource.Result{}, res) assert.Equal(t, expectedResult, res)
// Verify that the DATA_GENDER entry has been updated with the temporary value // Verify that the DATA_GENDER entry has been updated with the temporary value
storedGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_GENDER) storedGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_GENDER)
@@ -1498,10 +1530,10 @@ func TestValidateRecipient(t *testing.T) {
}{ }{
{ {
name: "Test with invalid recepient", name: "Test with invalid recepient",
input: []byte("9234adf5"), input: []byte("7?1234"),
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagSet: []uint32{flag_invalid_recipient}, FlagSet: []uint32{flag_invalid_recipient},
Content: "9234adf5", Content: "7?1234",
}, },
}, },
{ {
@@ -1517,22 +1549,40 @@ func TestValidateRecipient(t *testing.T) {
input: []byte("0711223344"), input: []byte("0711223344"),
expectedResult: resource.Result{}, expectedResult: resource.Result{},
}, },
{
name: "Test with address",
input: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"),
expectedResult: resource.Result{},
},
{
name: "Test with alias recepient",
input: []byte("alias123"),
expectedResult: resource.Result{},
},
} }
// store a public key for the valid recipient // store a public key for the valid recipient
err = store.WriteEntry(ctx, "0711223344", common.DATA_PUBLIC_KEY, []byte(publicKey)) err = store.WriteEntry(ctx, "+254711223344", common.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
mockAccountService := new(mocks.MockAccountService)
// Create the Handlers instance // Create the Handlers instance
h := &Handlers{ h := &Handlers{
flagManager: fm.parser, flagManager: fm.parser,
userdataStore: store, userdataStore: store,
accountService: mockAccountService,
} }
aliasResponse := &dataserviceapi.AliasAddress{
Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
}
mockAccountService.On("CheckAliasAddress", string(tt.input)).Return(aliasResponse, nil)
// Call the method // Call the method
res, err := h.ValidateRecipient(ctx, "validate_recepient", tt.input) res, err := h.ValidateRecipient(ctx, "validate_recepient", tt.input)
@@ -1564,7 +1614,7 @@ func TestCheckBalance(t *testing.T) {
publicKey: "0X98765432109", publicKey: "0X98765432109",
activeSym: "ETH", activeSym: "ETH",
activeBal: "1.5", activeBal: "1.5",
expectedResult: resource.Result{Content: "Balance: 1.5 ETH\n"}, expectedResult: resource.Result{Content: "Balance: 1.50 ETH\n"},
expectError: false, expectError: false,
}, },
} }
@@ -1919,7 +1969,7 @@ func TestCheckVouchers(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// Read voucher sym data from the store // Read voucher sym data from the store
voucherData, err := spdb.Get(ctx, []byte("sym")) voucherData, err := spdb.Get(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1943,7 +1993,7 @@ func TestGetVoucherList(t *testing.T) {
expectedSym := []byte("1:SRF\n2:MILO") expectedSym := []byte("1:SRF\n2:MILO")
// Put voucher sym data from the store // Put voucher sym data from the store
err := spdb.Put(ctx, []byte("sym"), expectedSym) err := spdb.Put(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS), expectedSym)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1973,16 +2023,16 @@ func TestViewVoucher(t *testing.T) {
} }
// Define mock voucher data // Define mock voucher data
mockData := map[string][]byte{ mockData := map[common.DataTyp][]byte{
"sym": []byte("1:SRF\n2:MILO"), common.DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"),
"bal": []byte("1:100\n2:200"), common.DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"),
"deci": []byte("1:6\n2:4"), common.DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"),
"addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), common.DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
} }
// Put the data // Put the data
for key, value := range mockData { for key, value := range mockData {
err = spdb.Put(ctx, []byte(key), []byte(value)) err = spdb.Put(ctx, []byte(common.ToBytes(key)), []byte(value))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1990,7 +2040,7 @@ func TestViewVoucher(t *testing.T) {
res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1")) res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1"))
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, res.Content, "SRF\n100") assert.Equal(t, res.Content, "Symbol: SRF\nBalance: 100")
} }
func TestSetVoucher(t *testing.T) { func TestSetVoucher(t *testing.T) {
@@ -2024,3 +2074,42 @@ func TestSetVoucher(t *testing.T) {
assert.Equal(t, string(tempData.TokenSymbol), res.Content) assert.Equal(t, string(tempData.TokenSymbol), res.Content)
} }
func TestGetVoucherDetails(t *testing.T) {
ctx, store := InitializeTestStore(t)
fm, err := NewFlagManager(flagsPath)
if err != nil {
t.Logf(err.Error())
}
mockAccountService := new(mocks.MockAccountService)
sessionId := "session123"
ctx = context.WithValue(ctx, "SessionId", sessionId)
expectedResult := resource.Result{}
tokA_AAddress := "0x0000000000000000000000000000000000000000"
h := &Handlers{
userdataStore: store,
flagManager: fm.parser,
accountService: mockAccountService,
}
err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_ADDRESS, []byte(tokA_AAddress))
if err != nil {
t.Fatal(err)
}
tokenDetails := &models.VoucherDataResult{
TokenName: "Token A",
TokenSymbol: "TOKA",
TokenLocation: "Kilifi,Kenya",
TokenCommodity: "Farming",
}
expectedResult.Content = fmt.Sprintf(
"Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", tokenDetails.TokenName, tokenDetails.TokenSymbol, tokenDetails.TokenCommodity, tokenDetails.TokenLocation,
)
mockAccountService.On("VoucherData", string(tokA_AAddress)).Return(tokenDetails, nil)
res, err := h.GetVoucherDetails(ctx, "SessionId", []byte(""))
assert.NoError(t, err)
assert.Equal(t, expectedResult, res)
}

View File

@@ -6,10 +6,6 @@ import (
"git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/db"
) )
const (
DATATYPE_USERSUB = 64
)
// PrefixDb interface abstracts the database operations. // PrefixDb interface abstracts the database operations.
type PrefixDb interface { type PrefixDb interface {
Get(ctx context.Context, key []byte) ([]byte, error) Get(ctx context.Context, key []byte) ([]byte, error)
@@ -35,13 +31,13 @@ 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(db.DATATYPE_USERDATA)
key = s.toKey(key) key = s.toKey(key)
return s.store.Get(ctx, key) return s.store.Get(ctx, key)
} }
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error { func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
s.store.SetPrefix(DATATYPE_USERSUB) s.store.SetPrefix(db.DATATYPE_USERDATA)
key = s.toKey(key) key = s.toKey(key)
return s.store.Put(ctx, key, val) return s.store.Put(ctx, key, val)
} }

View File

@@ -49,6 +49,6 @@ func (m *MockAccountService) TokenTransfer(ctx context.Context, amount, from, to
} }
func (m *MockAccountService) CheckAliasAddress(ctx context.Context, alias string) (*dataserviceapi.AliasAddress, error) { func (m *MockAccountService) CheckAliasAddress(ctx context.Context, alias string) (*dataserviceapi.AliasAddress, error) {
args := m.Called() args := m.Called(alias)
return args.Get(0).(*dataserviceapi.AliasAddress), args.Error(1) return args.Get(0).(*dataserviceapi.AliasAddress), args.Error(1)
} }

View File

@@ -1,6 +1,9 @@
package utils package utils
import "time" import (
"strconv"
"time"
)
// CalculateAge calculates the age based on a given birthdate and the current date in the format dd/mm/yy // CalculateAge calculates the age based on a given birthdate and the current date in the format dd/mm/yy
// It adjusts for cases where the current date is before the birthday in the current year. // It adjusts for cases where the current date is before the birthday in the current year.
@@ -25,11 +28,29 @@ func CalculateAge(birthdate, today time.Time) int {
// It subtracts the YOB from the current year to determine the age. // It subtracts the YOB from the current year to determine the age.
// //
// Parameters: // Parameters:
// yob: The year of birth as an integer. //
// yob: The year of birth as an integer.
// //
// Returns: // Returns:
// The calculated age as an integer. //
// The calculated age as an integer.
func CalculateAgeWithYOB(yob int) int { func CalculateAgeWithYOB(yob int) int {
currentYear := time.Now().Year() currentYear := time.Now().Year()
return currentYear - yob return currentYear - yob
} }
//IsValidYob checks if the provided yob can be considered valid
func IsValidYOb(yob string) bool {
currentYear := time.Now().Year()
yearOfBirth, err := strconv.ParseInt(yob, 10, 64)
if err != nil {
return false
}
if yearOfBirth >= 1900 && int(yearOfBirth) <= currentYear {
return true
} else {
return false
}
}

17
internal/utils/name.go Normal file
View File

@@ -0,0 +1,17 @@
package utils
func ConstructName(firstName, familyName, defaultValue string) string {
name := defaultValue
if familyName != defaultValue {
if firstName != defaultValue {
name = firstName + " " + familyName
} else {
name = familyName
}
} else {
if firstName != defaultValue {
name = firstName
}
}
return name
}

View File

@@ -54,7 +54,7 @@
}, },
{ {
"input": "1235", "input": "1235",
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit" "expectedContent": "Incorrect PIN\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@@ -62,7 +62,7 @@
}, },
{ {
"input": "1234", "input": "1234",
"expectedContent": "Select language:\n0:english\n1:kiswahili" "expectedContent": "Select language:\n0:English\n1:Kiswahili"
}, },
{ {
"input": "0", "input": "0",
@@ -95,7 +95,7 @@
}, },
{ {
"input": "1235", "input": "1235",
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit" "expectedContent": "Incorrect PIN\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@@ -141,7 +141,7 @@
}, },
{ {
"input": "1235", "input": "1235",
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit" "expectedContent": "Incorrect PIN\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@@ -167,7 +167,7 @@
] ]
}, },
{ {
"name": "menu_my_account_edit_firstname", "name": "menu_my_account_edit_all_account_details_starting_from_firstname",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@@ -187,6 +187,26 @@
}, },
{ {
"input": "foo", "input": "foo",
"expectedContent": "Enter family name:\n0:Back"
},
{
"input": "bar",
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:" "expectedContent": "Please enter your PIN:"
}, },
{ {
@@ -197,10 +217,6 @@
"input": "0", "input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back" "expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
}, },
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{ {
"input": "0", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@@ -208,7 +224,7 @@
] ]
}, },
{ {
"name": "menu_my_account_edit_familyname", "name": "menu_my_account_edit_familyname_when_all_account__details_have_been_set",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@@ -238,10 +254,6 @@
"input": "0", "input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back" "expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
}, },
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{ {
"input": "0", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@@ -250,7 +262,7 @@
] ]
}, },
{ {
"name": "menu_my_account_edit_gender", "name": "menu_my_account_edit_gender_when_all_account__details_have_been_set",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@@ -280,10 +292,6 @@
"input": "0", "input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back" "expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
}, },
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{ {
"input": "0", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@@ -291,7 +299,7 @@
] ]
}, },
{ {
"name": "menu_my_account_edit_yob", "name": "menu_my_account_edit_yob_when_all_account__details_have_been_set",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@@ -321,10 +329,6 @@
"input": "0", "input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back" "expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
}, },
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{ {
"input": "0", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@@ -332,7 +336,7 @@
] ]
}, },
{ {
"name": "menu_my_account_edit_location", "name": "menu_my_account_edit_location_when_all_account_details_have_been_set",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@@ -362,10 +366,6 @@
"input": "0", "input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back" "expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
}, },
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{ {
"input": "0", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@@ -373,7 +373,7 @@
] ]
}, },
{ {
"name": "menu_my_account_edit_offerings", "name": "menu_my_account_edit_offerings_when_all_account__details_have_been_set",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@@ -403,10 +403,6 @@
"input": "0", "input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back" "expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
}, },
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{ {
"input": "0", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@@ -434,16 +430,12 @@
}, },
{ {
"input": "1234", "input": "1234",
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 79\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back" "expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 84\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back"
}, },
{ {
"input": "0", "input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back" "expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
}, },
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{ {
"input": "0", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"

View File

@@ -3,6 +3,7 @@ package menutraversaltest
import ( import (
"bytes" "bytes"
"context" "context"
"flag"
"log" "log"
"math/rand" "math/rand"
"os" "os"
@@ -15,14 +16,15 @@ import (
) )
var ( var (
testData = driver.ReadData() testData = driver.ReadData()
testStore = ".test_state" testStore = ".test_state"
groupTestFile = "group_test.json" sessionID string
sessionID string src = rand.NewSource(42)
src = rand.NewSource(42) g = rand.New(src)
g = rand.New(src)
) )
var groupTestFile = flag.String("test-file", "group_test.json", "The test file to use for running the group tests")
func GenerateSessionId() string { func GenerateSessionId() string {
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g)) uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
v, err := uu.NewV4() v, err := uu.NewV4()
@@ -337,7 +339,7 @@ func TestMainMenuSend(t *testing.T) {
} }
func TestGroups(t *testing.T) { func TestGroups(t *testing.T) {
groups, err := driver.LoadTestGroups(groupTestFile) groups, err := driver.LoadTestGroups(*groupTestFile)
if err != nil { if err != nil {
log.Fatalf("Failed to load test groups: %v", err) log.Fatalf("Failed to load test groups: %v", err)
} }

View File

@@ -0,0 +1,68 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_family_name",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "2",
"expectedContent": "Enter family name:\n0:Back"
},
{
"input": "bar",
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@@ -0,0 +1,61 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_firstname",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your first names:\n0:Back"
},
{
"input": "foo",
"expectedContent": "Enter family name:\n0:Back"
},
{
"input": "bar",
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@@ -0,0 +1,55 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_gender",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "3",
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@@ -0,0 +1,46 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_location",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "5",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@@ -0,0 +1,42 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_offerings",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "6",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@@ -0,0 +1,50 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_yob",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "4",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@@ -0,0 +1,70 @@
{
"groups": [
{
"name": "menu_my_account_edit_familyname_when_adjacent_profile_information_set",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "3",
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "2",
"expectedContent": "Enter family name:\n0:Back"
},
{
"input": "foo2",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@@ -7,11 +7,11 @@
"steps": [ "steps": [
{ {
"input": "", "input": "",
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:english\n1:kiswahili" "expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:English\n1:Kiswahili"
}, },
{ {
"input": "0", "input": "0",
"expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no" "expectedContent": "Do you agree to terms and conditions?\n0:Yes\n1:No"
}, },
{ {
"input": "0", "input": "0",
@@ -40,11 +40,11 @@
"steps": [ "steps": [
{ {
"input": "", "input": "",
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:english\n1:kiswahili" "expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:English\n1:Kiswahili"
}, },
{ {
"input": "0", "input": "0",
"expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no" "expectedContent": "Do you agree to terms and conditions?\n0:Yes\n1:No"
}, },
{ {
"input": "1", "input": "1",

18
models/profile.go Normal file
View File

@@ -0,0 +1,18 @@
package models
type Profile struct {
ProfileItems []string
Max int
}
func (p *Profile) InsertOrShift(index int, value string) {
if index < len(p.ProfileItems) {
p.ProfileItems = append(p.ProfileItems[:index], value)
} else {
for len(p.ProfileItems) < index {
p.ProfileItems = append(p.ProfileItems, "0")
}
p.ProfileItems = append(p.ProfileItems, "0")
p.ProfileItems[index] = value
}
}

View File

@@ -1,8 +1,10 @@
package models package models
type VoucherDataResult struct { type VoucherDataResult struct {
TokenName string `json:"tokenName"` TokenName string `json:"tokenName"`
TokenSymbol string `json:"tokenSymbol"` TokenSymbol string `json:"tokenSymbol"`
TokenDecimals int `json:"tokenDecimals"` TokenDecimals int `json:"tokenDecimals"`
SinkAddress string `json:"sinkAddress"` SinkAddress string `json:"sinkAddress"`
TokenCommodity string `json:"tokenCommodity"`
TokenLocation string `json:"tokenLocation"`
} }

View File

@@ -0,0 +1 @@
Tatizo la kimtambo limetokea,tafadhali jaribu tena baadaye.

View File

@@ -1,10 +1,7 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_familyname flag_allow_update 1
LOAD get_current_profile_info 0 LOAD get_current_profile_info 0
RELOAD get_current_profile_info RELOAD get_current_profile_info
MAP get_current_profile_info
MOUT back 0 MOUT back 0
HALT HALT
LOAD save_familyname 0
RELOAD save_familyname
INCMP _ 0 INCMP _ 0
INCMP pin_entry * INCMP edit_family_name_next *

View File

@@ -0,0 +1,11 @@
LOAD save_familyname 32
RELOAD save_familyname
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_familyname flag_allow_update 1
RELOAD set_back
CATCH _ flag_back_set 1
CATCH pin_entry flag_familyname_set 1
CATCH select_gender flag_gender_set 0
CATCH edit_yob flag_yob_set 0
CATCH edit_location flag_location_set 0
CATCH pin_entry flag_familyname_set 0

View File

@@ -1,11 +1,7 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_firstname flag_allow_update 1
LOAD get_current_profile_info 0 LOAD get_current_profile_info 0
RELOAD get_current_profile_info RELOAD get_current_profile_info
MAP get_current_profile_info MAP get_current_profile_info
MOUT back 0 MOUT back 0
HALT HALT
LOAD save_firstname 0
RELOAD save_firstname
INCMP _ 0 INCMP _ 0
INCMP pin_entry * INCMP edit_first_name_next *

View File

@@ -0,0 +1,12 @@
LOAD save_firstname 64
RELOAD save_firstname
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_firstname flag_allow_update 1
RELOAD set_back
CATCH _ flag_back_set 1
CATCH pin_entry flag_firstname_set 1
CATCH edit_family_name flag_familyname_set 0
CATCH select_gender flag_gender_set 0
CATCH edit_yob flag_yob_set 0
CATCH edit_location flag_location_set 0
CATCH pin_entry flag_firstname_set 0

View File

@@ -1,10 +1,7 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_location flag_allow_update 1
LOAD get_current_profile_info 0 LOAD get_current_profile_info 0
RELOAD get_current_profile_info RELOAD get_current_profile_info
MOUT back 0 MOUT back 0
HALT HALT
LOAD save_location 0
RELOAD save_location
INCMP _ 0 INCMP _ 0
INCMP pin_entry * INCMP edit_location_next *

View File

@@ -0,0 +1,8 @@
LOAD save_location 16
RELOAD save_location
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_location flag_allow_update 1
RELOAD set_back
CATCH _ flag_back_set 1
CATCH pin_entry flag_location_set 1
CATCH edit_offerings flag_offerings_set 0

View File

@@ -1,10 +1,7 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_offerings flag_allow_update 1
LOAD get_current_profile_info 0 LOAD get_current_profile_info 0
RELOAD get_current_profile_info RELOAD get_current_profile_info
LOAD save_offerings 0
MOUT back 0 MOUT back 0
HALT HALT
RELOAD save_offerings
INCMP _ 0 INCMP _ 0
INCMP pin_entry * INCMP edit_offerings_next *

View File

@@ -0,0 +1,8 @@
LOAD save_offerings 8
RELOAD save_offerings
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_offerings flag_allow_update 1
RELOAD set_back
CATCH _ flag_back_set 1
CATCH pin_entry flag_offerings_set 1
MOVE update_profile_items

View File

@@ -11,7 +11,8 @@ MOUT edit_offerings 6
MOUT view 7 MOUT view 7
MOUT back 0 MOUT back 0
HALT HALT
INCMP my_account 0 LOAD set_back 6
INCMP ^ 0
INCMP edit_first_name 1 INCMP edit_first_name 1
INCMP edit_family_name 2 INCMP edit_family_name 2
INCMP select_gender 3 INCMP select_gender 3

View File

@@ -1,14 +1,7 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_yob flag_allow_update 1
LOAD get_current_profile_info 0 LOAD get_current_profile_info 0
RELOAD get_current_profile_info RELOAD get_current_profile_info
MAP get_current_profile_info MAP get_current_profile_info
MOUT back 0 MOUT back 0
HALT HALT
LOAD verify_yob 6
RELOAD verify_yob
CATCH incorrect_date_format flag_incorrect_date_format 1
LOAD save_yob 0
RELOAD save_yob
INCMP _ 0 INCMP _ 0
INCMP pin_entry * INCMP edit_yob_next *

View File

@@ -0,0 +1,13 @@
LOAD save_yob 16
LOAD verify_yob 8
RELOAD verify_yob
RELOAD save_yob
CATCH incorrect_date_format flag_incorrect_date_format 1
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_yob flag_allow_update 1
RELOAD set_back
CATCH _ flag_back_set 1
CATCH pin_entry flag_yob_set 1
CATCH edit_location flag_location_set 0
CATCH edit_offerings flag_offerings_set 0
CATCH pin_entry flag_yob_set 0

View File

@@ -0,0 +1 @@
English

View File

@@ -2,5 +2,5 @@ LOAD reset_incorrect_date_format 8
MOUT retry 1 MOUT retry 1
MOUT quit 9 MOUT quit 9
HALT HALT
INCMP _ 1 INCMP ^ 1
INCMP quit 9 INCMP quit 9

View File

@@ -1 +1 @@
Incorrect pin Incorrect PIN

View File

@@ -1 +1 @@
PIN mpya na udhibitisho wa pin mpya hazilingani.Tafadhali jaribu tena.Kwa usaidizi piga simu +254757628885. PIN mpya na udhibitisho wa PIN mpya hazilingani.Tafadhali jaribu tena.Kwa usaidizi piga simu +254757628885.

View File

@@ -0,0 +1 @@
Kiswahili

View File

@@ -13,7 +13,7 @@ msgstr "Kwa usaidizi zaidi,piga: 0757628885"
msgid "Balance: %s\n" msgid "Balance: %s\n"
msgstr "Salio: %s\n" msgstr "Salio: %s\n"
msid "Your invite request for %s to Sarafu Network failed. Please try again later." msgid "Your invite request for %s to Sarafu Network failed. Please try again later."
msgstr "Ombi lako la kumwalika %s kwa matandao wa Sarafu halikufaulu. Tafadhali jaribu tena baadaye." msgstr "Ombi lako la kumwalika %s kwa matandao wa Sarafu halikufaulu. Tafadhali jaribu tena baadaye."
msgid "Your invitation to %s to join Sarafu Network has been sent." msgid "Your invitation to %s to join Sarafu Network has been sent."
@@ -23,4 +23,7 @@ msgid "Your request failed. Please try again later."
msgstr "Ombi lako halikufaulu. Tafadhali jaribu tena baadaye." msgstr "Ombi lako halikufaulu. Tafadhali jaribu tena baadaye."
msgid "Community Balance: 0.00" msgid "Community Balance: 0.00"
msgid "Salio la Kikundi: 0.00" msgstr "Salio la Kikundi: 0.00"
msgid "Symbol: %s\nBalance: %s"
msgstr "Sarafu: %s\nSalio: %s"

View File

@@ -1,10 +1,10 @@
LOAD set_default_voucher 8 LOAD set_default_voucher 8
RELOAD set_default_voucher RELOAD set_default_voucher
LOAD check_balance 64
RELOAD check_balance
LOAD check_vouchers 10 LOAD check_vouchers 10
RELOAD check_vouchers RELOAD check_vouchers
CATCH api_failure flag_api_call_error 1 LOAD check_balance 128
RELOAD check_balance
CATCH api_failure flag_api_call_error 1
MAP check_balance MAP check_balance
MOUT send 1 MOUT send 1
MOUT vouchers 2 MOUT vouchers 2

View File

@@ -1 +1 @@
Salio lako ni: 0.00 SRF {{.check_balance}}

View File

@@ -0,0 +1 @@
Next

View File

@@ -0,0 +1 @@
Mbele

View File

@@ -1 +1 @@
no No

View File

@@ -1 +1 @@
la La

View File

@@ -21,3 +21,10 @@ 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 flag,flag_unregistered_number,28,this is set when an unregistered phonenumber tries to perform an action
flag,flag_no_transfers,29,this is set when a user does not have any transactions flag,flag_no_transfers,29,this is set when a user does not have any transactions
flag,flag_incorrect_statement,30,this is set when the selected statement is invalid flag,flag_incorrect_statement,30,this is set when the selected statement is invalid
flag,flag_firstname_set,31,this is set when the first name of the profile is set
flag,flag_familyname_set,32,this is set when the family name of the profile is set
flag,flag_yob_set,33,this is set when the yob of the profile is set
flag,flag_gender_set,34,this is set when the gender of the profile is set
flag,flag_location_set,35,this is set when the location of the profile is set
flag,flag_offerings_set,36,this is set when the offerings of the profile is set
flag,flag_back_set,37,this is set when it is a back navigation
1 flag flag_language_set 8 checks whether the user has set their prefered language
21 flag flag_unregistered_number 28 this is set when an unregistered phonenumber tries to perform an action
22 flag flag_no_transfers 29 this is set when a user does not have any transactions
23 flag flag_incorrect_statement 30 this is set when the selected statement is invalid
24 flag flag_firstname_set 31 this is set when the first name of the profile is set
25 flag flag_familyname_set 32 this is set when the family name of the profile is set
26 flag flag_yob_set 33 this is set when the yob of the profile is set
27 flag flag_gender_set 34 this is set when the gender of the profile is set
28 flag flag_location_set 35 this is set when the location of the profile is set
29 flag flag_offerings_set 36 this is set when the offerings of the profile is set
30 flag flag_back_set 37 this is set when it is a back navigation

View File

@@ -0,0 +1 @@
Prev

View File

@@ -0,0 +1 @@
Nyuma

View File

@@ -1,3 +1,5 @@
LOAD update_all_profile_items 0
RELOAD update_all_profile_items
MOUT back 0 MOUT back 0
MOUT quit 9 MOUT quit 9
HALT HALT

View File

@@ -1,5 +1,3 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH profile_update_success flag_allow_update 1
LOAD get_current_profile_info 0 LOAD get_current_profile_info 0
RELOAD get_current_profile_info RELOAD get_current_profile_info
MOUT male 1 MOUT male 1

View File

@@ -1,3 +1,4 @@
LOAD set_language 6 LOAD set_language 6
RELOAD set_language
CATCH terms flag_account_created 0 CATCH terms flag_account_created 0
MOVE language_changed MOVE language_changed

View File

@@ -1,4 +1,3 @@
LOAD save_gender 0 LOAD save_gender 16
CATCH incorrect_pin flag_incorrect_pin 1 RELOAD save_gender
CATCH update_gender flag_allow_update 1 MOVE set_gender_next
MOVE pin_entry

View File

@@ -0,0 +1 @@
Select gender

View File

@@ -0,0 +1,6 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_gender flag_allow_update 1
CATCH pin_entry flag_gender_set 1
CATCH edit_yob flag_yob_set 0
CATCH edit_location flag_location_set 0
CATCH edit_offerings flag_offerings_set 0

View File

@@ -1,4 +1,3 @@
LOAD save_gender 0 LOAD save_gender 16
CATCH incorrect_pin flag_incorrect_pin 1 RELOAD save_gender
CATCH update_gender flag_allow_update 1 MOVE set_gender_next
MOVE pin_entry

View File

@@ -1,3 +1,4 @@
LOAD set_language 6 LOAD set_language 6
RELOAD set_language
CATCH terms flag_account_created 0 CATCH terms flag_account_created 0
MOVE language_changed MOVE language_changed

View File

@@ -1,4 +1,3 @@
LOAD save_gender 0 LOAD save_gender 16
CATCH incorrect_pin flag_incorrect_pin 1 RELOAD save_gender
CATCH update_gender flag_allow_update 1 MOVE set_gender_next
MOVE pin_entry

View File

@@ -0,0 +1,3 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH profile_update_success flag_allow_update 1
MOVE pin_entry

View File

@@ -1 +1 @@
yes Yes

View File

@@ -1 +1 @@
ndio Ndio