Compare commits

..

127 Commits

Author SHA1 Message Date
db5d55d8e3 Merge pull request 'route-api-errors' (#46) from route-api-errors into master
Reviewed-on: #46
2025-04-29 13:55:54 +02:00
06cab56427
fix failing tests: menuhandlers missing the logdb 2025-04-29 13:25:31 +03:00
d0c181bca2
Merge branch 'master' into route-api-errors 2025-04-29 12:58:46 +03:00
e379129b3d Merge pull request 'logdb' (#56) from logdb into master
Some checks are pending
release / docker (push) Waiting to run
Reviewed-on: #56
2025-04-29 09:15:50 +02:00
29d35d1ec4
add write of the temporary and confirmed account pins to the log db 2025-04-28 15:49:40 +03:00
687447e73e
added the logdb to the testutil engine 2025-04-28 15:09:38 +03:00
ec1ddefeb3
updated packages for sarafu-api and vise 2025-04-28 12:50:36 +03:00
4166c9aed5 Merge branch 'master' into logdb 2025-04-28 12:38:27 +03:00
78e82edfb5
added the logdn to the menuhandler tests 2025-04-28 12:26:06 +03:00
a5c4866b23
add required logdb to menuhandler 2025-04-28 11:50:25 +03:00
b52239e6be
use direct key data type 2025-04-28 11:50:10 +03:00
5f02df64d7
upgrade visedriver dep 2025-04-28 11:40:28 +03:00
0ba65fc48c
use the updated phone and alias regexes from common 2025-04-28 11:39:37 +03:00
ce8cc8e0fd
use latest commit from sarafu-api 2025-04-28 11:38:45 +03:00
1499e4a29a
check recipient before sending an sms 2025-04-28 11:38:44 +03:00
5f24ff3b1b
feat: send invites when a recipient
is not resolved
2025-04-28 11:38:44 +03:00
6cdee7b58b
update sarafu-api dep 2025-04-28 11:38:43 +03:00
af5537abfc
updated the description of DATA_SELF_PIN_RESET 2025-04-28 11:37:45 +03:00
a6e569afb1
remove the verify_new_pin functionality that managed the unused flag_valid_pin 2025-04-28 11:37:44 +03:00
446514d8ce
remove unused function reset_valid_pin 2025-04-28 11:37:44 +03:00
131d106f38
use a single confirm_pin_change node 2025-04-28 11:37:43 +03:00
4d62f0222e
use the same check for flag_invalid_pin to move to the invalid_pin node 2025-04-28 11:37:43 +03:00
37cc3e1bc1
cleanup: remove extra space in all .vis files 2025-04-28 11:37:42 +03:00
491e97d8af
cleanup: remove extra space 2025-04-28 11:37:42 +03:00
d4fefe3c39
update the order for the INCMP statement 2025-04-28 11:37:41 +03:00
e8ff468c7b
update the reset flags in TestCheckBlockedStatus 2025-04-28 11:37:41 +03:00
965f343230
update the reset flags in TestConfirmPinChange 2025-04-28 11:37:40 +03:00
3426d31a9e
use the correct flag_invalid_pin in TestSaveTemporaryPin 2025-04-28 11:37:40 +03:00
8b2b667307
updated the expected content for the alias in view profile 2025-04-28 11:37:39 +03:00
c206fa1329
updated the menu flow for the admin reset others PIN in test 2025-04-28 11:37:39 +03:00
ad395c4b1a
remove code related to admins resetting a user's actual PIN 2025-04-28 11:37:38 +03:00
af7e6cc603
add a space 2025-04-28 11:37:38 +03:00
f90a188acc
updated the flags set and reset in SaveTemporaryPin and VerifyCreatePin 2025-04-28 11:37:37 +03:00
7a542d1f1c
updated the description of the flag_incorrect_pin 2025-04-28 11:37:37 +03:00
7d800d68a0
have the catch for create_pin above to catch accounts that do not have a PIN before the status on chain is checked 2025-04-28 11:37:36 +03:00
850458b5d8
use a single pin_mismatch node for a confirmed PIN that does not match the original PIN 2025-04-28 11:37:36 +03:00
ef8e3d5c35
allow the user to quit and move to invalid_pin node if the given input is invalid 2025-04-28 11:37:35 +03:00
982eebf374
move to the top node when one selects back 2025-04-28 11:37:35 +03:00
228d936a0e
use the confirm_self_pin_reset to confirm the new PIN 2025-04-28 11:37:34 +03:00
7c4d37b5b8
use the self_reset_pin node to reset PINs for accounts with a flag_account_pin_reset 2025-04-28 11:37:33 +03:00
e6e8bb6671
set the DATA_SELF_PIN_RESET as 0 and reset the flag_account_pin_reset 2025-04-28 11:37:33 +03:00
6385735b89
move back to the previous node if the admin user is not authorized 2025-04-28 11:34:27 +03:00
70815aabd1
request the admin to authorize the PIN reset 2025-04-28 11:34:27 +03:00
919899c704
move to the authorize_reset_others_pin if the given number is valid and registered 2025-04-28 11:34:26 +03:00
9a2ad99d07
updated the ResetOthersPin and ValidateBlockedNumber for the updated reset other's PIN functionality 2025-04-28 11:34:26 +03:00
2f4959e191
use the DATA_SELF_PIN_RESET to set the flag_account_pin_reset 2025-04-28 11:34:25 +03:00
1b2f3bb046
added a DATA_SELF_PIN_RESET to track when a user's PIN is reset by an admin 2025-04-28 11:34:24 +03:00
19a2b28367 Merge pull request 'extra-sms' (#64) from extra-sms into master
Reviewed-on: #64
2025-04-28 10:33:25 +02:00
25d124c58d
use updated go-vise that removes rollback on missing key 2025-04-25 17:10:46 +03:00
5b34ef28eb
added the logDb to async and http 2025-04-25 09:33:08 +03:00
0370a3def4
use dedicated smsservice for triggering extra sms 2025-04-18 12:13:24 +03:00
7c0651d218
use dedicated sms package for triggering sms 2025-04-18 12:12:33 +03:00
a38ac06a3d
trigger an address sms in the check_identifier handler 2025-04-18 10:56:41 +03:00
ac2a3721b2
remove dedicated address sms trigger 2025-04-18 10:55:02 +03:00
7ea3cb6b51
remove reload for check identifier to prevent double address sms trigger 2025-04-18 10:54:23 +03:00
0274133d48
add handler to send an address sms 2025-04-17 23:32:05 +03:00
5ff2e794ff
feat: implement address and pin reset sms 2025-04-17 23:31:48 +03:00
08deeca500
register handler to trigger address sms 2025-04-17 23:31:22 +03:00
cea920c1f5
dep: upgrade sarafu-api dep to include address and pin reset sms 2025-04-17 23:30:54 +03:00
ade236747c Merge pull request 'use the updated phone and alias regexes from common' (#63) from update-recipient-validator into master
Reviewed-on: #63
2025-04-17 16:53:42 +02:00
e1a3729e3b
use the updated phone and alias regexes from common 2025-04-17 14:31:18 +03:00
f127fd7c0f
initialize log db ,attach it to the menuhandler 2025-04-14 11:34:37 +03:00
42177aadeb
use latest commit from sarafu-api
Some checks failed
release / docker (push) Has been cancelled
2025-04-11 11:10:48 +03:00
fa9f1a72ef Merge pull request 'self pin reset' (#49) from self-pin-reset-node into master
Reviewed-on: #49
2025-04-11 09:26:11 +02:00
4ffb32f14a Merge branch 'master' into self-pin-reset-node 2025-04-11 10:25:35 +03:00
c41277db87 Merge pull request 'sms-upsell' (#55) from sms-upsell into master
Reviewed-on: #55
2025-04-11 09:24:35 +02:00
56cda36aa7
updated the description of DATA_SELF_PIN_RESET 2025-04-10 13:55:00 +03:00
c9170ca45a Merge branch 'master' into self-pin-reset-node 2025-04-09 23:43:10 +03:00
57a1645c03
remove the verify_new_pin functionality that managed the unused flag_valid_pin 2025-04-09 23:40:03 +03:00
7d9c3b66a9
remove unused function reset_valid_pin 2025-04-09 19:29:40 +03:00
aec1f4c4c1
use a single confirm_pin_change node 2025-04-09 19:23:46 +03:00
d680387ef1
use the same check for flag_invalid_pin to move to the invalid_pin node 2025-04-09 19:21:35 +03:00
0337c66f96
cleanup: remove extra space in all .vis files 2025-04-09 18:17:59 +03:00
6070792fe4
cleanup: remove extra space 2025-04-09 18:14:10 +03:00
69ae494b2c
update the order for the INCMP statement 2025-04-09 18:10:51 +03:00
b89abf3487
update the reset flags in TestCheckBlockedStatus 2025-04-09 18:06:24 +03:00
4ef8c47f8b
update the reset flags in TestConfirmPinChange 2025-04-09 18:01:40 +03:00
1076a9578e
use the correct flag_invalid_pin in TestSaveTemporaryPin 2025-04-09 17:57:54 +03:00
a27d44e561
updated the expected content for the alias in view profile 2025-04-09 17:52:58 +03:00
35a2732fe2
updated the menu flow for the admin reset others PIN in test 2025-04-09 17:52:18 +03:00
834f2ce629
remove code related to admins resetting a user's actual PIN 2025-04-09 17:39:41 +03:00
26353bdf6e
add a space 2025-04-09 17:34:24 +03:00
1f6bf2bbed
updated the flags set and reset in SaveTemporaryPin and VerifyCreatePin 2025-04-09 16:59:48 +03:00
0c1d9ab582
updated the description of the flag_incorrect_pin 2025-04-09 16:58:13 +03:00
29d94bb2e5
have the catch for create_pin above to catch accounts that do not have a PIN before the status on chain is checked 2025-04-09 16:57:22 +03:00
067c496244
use a single pin_mismatch node for a confirmed PIN that does not match the original PIN 2025-04-09 16:55:46 +03:00
f8c258a3b4
allow the user to quit and move to invalid_pin node if the given input is invalid 2025-04-09 16:49:04 +03:00
aec96ce9ba
move to the top node when one selects back 2025-04-09 15:19:23 +03:00
71ef950fff
use the confirm_self_pin_reset to confirm the new PIN 2025-04-09 15:12:51 +03:00
477f3a307a
use the self_reset_pin node to reset PINs for accounts with a flag_account_pin_reset 2025-04-09 15:11:51 +03:00
33c376c971
set the DATA_SELF_PIN_RESET as 0 and reset the flag_account_pin_reset 2025-04-09 15:10:12 +03:00
79bf09f3d1
add logdb to menuhandler and associated write operations 2025-04-09 14:55:35 +03:00
f4804546d9
add log db struct with associated methods 2025-04-09 14:52:57 +03:00
3fff03a164
update visedriver dep 2025-04-09 14:50:28 +03:00
094866c129
add a logdb field to lhs and setter function 2025-04-09 14:49:41 +03:00
514f6ae05b
move back to the previous node if the admin user is not authorized 2025-04-08 16:26:21 +03:00
8a6659a98b
request the admin to authorize the PIN reset 2025-04-08 16:25:50 +03:00
d094af9c51
move to the authorize_reset_others_pin if the given number is valid and registered 2025-04-08 16:25:11 +03:00
f96f9c11e6
updated the ResetOthersPin and ValidateBlockedNumber for the updated reset other's PIN functionality 2025-04-08 16:23:21 +03:00
1d8b0ef951
Merge branch 'master' into sms-upsell 2025-04-07 11:45:29 +03:00
fb2dc003b8
check recipient before sending an sms 2025-04-07 11:38:52 +03:00
2c671bc0d4
use the DATA_SELF_PIN_RESET to set the flag_account_pin_reset 2025-04-02 18:29:04 +03:00
c5673b339b
added a DATA_SELF_PIN_RESET to track when a user's PIN is reset by an admin 2025-04-02 18:24:34 +03:00
58242c8d55
fix failing test: TestManageVouchers 2025-04-02 10:53:36 +03:00
cab90ed89a
set and reset flag_api_error flag 2025-04-02 10:48:04 +03:00
73c3486400
catch api call failure when manage vouchers is called 2025-04-02 10:47:33 +03:00
3c84fb5ae7
Merge branch 'master' into route-api-errors 2025-04-02 10:38:47 +03:00
695bfed349
feat: send invites when a recipient
is not resolved
2025-04-01 16:30:08 +03:00
8bf48cb081
update sarafu-api dep 2025-04-01 16:25:59 +03:00
04880b58a8
fix failing test 2025-04-01 12:49:10 +03:00
0458ac9498
fix failing test 2025-04-01 12:38:57 +03:00
a7e8c184f5
add some spacing 2025-04-01 11:59:31 +03:00
3615348efd
add swahili template 2025-04-01 11:57:59 +03:00
d0be79d817
set account creation failed flag 2025-04-01 11:30:00 +03:00
7883063e53
feat: set and reset api call failure flags 2025-03-31 15:42:27 +03:00
f562ce8adf
check for api call failure when setting the dafault voucher 2025-03-31 15:41:49 +03:00
cd2f4328a8
check for api call failure when checking transactions 2025-03-31 15:41:19 +03:00
95b9a6e486
catch api call failure when fetching voucher details 2025-03-28 16:09:53 +03:00
9674a04cbc
add handler to reset api call failure flag,set flag when requesting an alias fails 2025-03-28 15:51:56 +03:00
6c46c097fb
add explicit api call failure flag reset 2025-03-28 15:47:34 +03:00
c814c4ae5c
register handler to reset api call failure flag 2025-03-28 15:46:57 +03:00
bbecec0310
catch api call error when requesting an alias 2025-03-28 15:46:21 +03:00
eaa89c29df
Merge branch 'master' into route-api-errors 2025-03-28 15:22:40 +03:00
e832f46d22
return to top on retry 2025-03-26 14:40:54 +03:00
039117f40e
add api call failure catch 2025-03-26 14:40:32 +03:00
3532f72fbd
remove unneccessary catch 2025-03-26 14:39:37 +03:00
04c7e20457
set api call error flag to failing api calls 2025-03-26 14:38:47 +03:00
63 changed files with 693 additions and 607 deletions

View File

@ -44,6 +44,7 @@ func main() {
var err error
var gettextDir string
var langs args.LangVar
var logDbConnStr string
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
@ -55,6 +56,7 @@ func main() {
flag.UintVar(&port, "p", config.Port(), "http port")
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
flag.Var(&langs, "language", "add symbol resolution for language")
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
flag.Parse()
config.Apply(override)
@ -100,6 +102,11 @@ func main() {
fmt.Fprintf(os.Stderr, "userdatadb: %v\n", err)
os.Exit(1)
}
logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data")
if err != nil {
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
os.Exit(1)
}
dbResource, ok := rs.(*resource.DbResource)
if !ok {
@ -113,6 +120,7 @@ func main() {
os.Exit(1)
}
lhs.SetDataStore(&userdataStore)
lhs.SetLogDb(&logdb)
if err != nil {
fmt.Fprintf(os.Stderr, "setdatastore: %v\n", err)
os.Exit(1)

View File

@ -56,6 +56,7 @@ func main() {
var err error
var gettextDir string
var langs args.LangVar
var logDbConnStr string
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
@ -69,6 +70,7 @@ func main() {
flag.UintVar(&port, "p", config.Port(), "http port")
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
flag.Var(&langs, "language", "add symbol resolution for language")
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
flag.Parse()
config.Apply(override)
@ -120,6 +122,12 @@ func main() {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data")
if err != nil {
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
os.Exit(1)
}
//defer userdataStore.Close(ctx)
dbResource, ok := rs.(*resource.DbResource)
@ -129,6 +137,7 @@ func main() {
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore)
lhs.SetLogDb(&logdb)
accountService := services.New(ctx, menuStorageService)
hl, err := lhs.GetHandler(accountService)

View File

@ -43,6 +43,7 @@ func main() {
var err error
var gettextDir string
var langs args.LangVar
var logDbConnStr string
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
@ -55,6 +56,7 @@ func main() {
flag.UintVar(&port, "p", config.Port(), "http port")
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
flag.Var(&langs, "language", "add symbol resolution for language")
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
flag.Parse()
config.Apply(override)
@ -103,6 +105,12 @@ func main() {
os.Exit(1)
}
logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data")
if err != nil {
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
os.Exit(1)
}
dbResource, ok := rs.(*resource.DbResource)
if !ok {
os.Exit(1)
@ -110,6 +118,7 @@ func main() {
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore)
lhs.SetLogDb(&logdb)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())

View File

@ -36,6 +36,7 @@ func main() {
var err error
var gettextDir string
var langs args.LangVar
var logDbConnStr string
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
@ -46,6 +47,7 @@ func main() {
flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
flag.Var(&langs, "language", "add symbol resolution for language")
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
flag.Parse()
config.Apply(override)
@ -110,6 +112,12 @@ func main() {
os.Exit(1)
}
logdb, err := menuStorageService.GetLogDb(ctx, userdatastore, logDbConnStr, "user-data")
if err != nil {
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
os.Exit(1)
}
dbResource, ok := rs.(*resource.DbResource)
if !ok {
fmt.Fprintf(os.Stderr, "get dbresource error: %v\n", err)
@ -118,6 +126,7 @@ func main() {
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdatastore)
lhs.SetLogDb(&logdb)
lhs.SetPersister(pe)
if err != nil {
fmt.Fprintf(os.Stderr, "localhandler service error: %v\n", err)

8
go.mod
View File

@ -3,10 +3,10 @@ module git.grassecon.net/grassrootseconomics/sarafu-vise
go 1.23.4
require (
git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805
git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2
git.defalsify.org/vise.git v0.3.2-0.20250425131748-8b84f59792ce
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694
github.com/alecthomas/assert/v2 v2.2.2
github.com/gofrs/uuid v4.4.0+incompatible

18
go.sum
View File

@ -1,13 +1,11 @@
git.defalsify.org/vise.git v0.3.1 h1:A6FhMcur09ft/JzUPGXR+KpA17fltfeBnasyvLMZmq4=
git.defalsify.org/vise.git v0.3.1/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805 h1:FnT39aqXcP5YWhwPDBABopSjCu2SlbPFoOVitSpAVxU=
git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d h1:5mzLas+jxTUtusOKx4XvU+n2QvrV/mH17MnJRy46siQ=
git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b h1:xiTpaqWWoF5qcnarY/9ZkT6aVdnKwqztb2gzIahJn4w=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8=
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2 h1:YFztSsexCUgFo6M0tbngRwYdgJd3LQV3RO/Jw09u3+k=
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2/go.mod h1:6B6ByxXOiRY0NR7K02Bf3fEu7z+2c/6q8PFVNjC5G8w=
git.defalsify.org/vise.git v0.3.2-0.20250425131748-8b84f59792ce h1:Uke2jQ4wG97gQKnTzxPyBGyhosrU1IWnRNFHtKVrmrk=
git.defalsify.org/vise.git v0.3.2-0.20250425131748-8b84f59792ce/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e h1:DcC9qkNl9ny3hxQmsMK6W81+5R/j4ZwYUbvewMI/rlc=
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f h1:OAHCP3YR1C5h1WFnnEnLs5kn6jTxQHQYWYtQaMZJIMY=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8=
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 h1:Jo+yWysWw/N5BJQtAyEMN8ePVvAyPHv+JG4lQti+1N4=
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306/go.mod h1:FdLwYtzsjOIcDiW4uDgDYnB4Wdzq12uJUe0QHSSPbSo=
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E=
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694/go.mod h1:DpibtYpnT3nG4Kn556hRAkdu4+CtiI/6MbnQHal51mQ=
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=

View File

@ -29,6 +29,7 @@ import (
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
"git.grassecon.net/grassrootseconomics/sarafu-vise/internal/sms"
"git.grassecon.net/grassrootseconomics/sarafu-vise/profile"
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
@ -77,18 +78,28 @@ type MenuHandlers struct {
flagManager *FlagManager
accountService remote.AccountService
prefixDb storedb.PrefixDb
smsService sms.SmsService
logDb store.LogDb
profile *profile.Profile
ReplaceSeparatorFunc func(string) string
}
// NewHandlers creates a new instance of the Handlers struct with the provided dependencies.
func NewMenuHandlers(appFlags *FlagManager, userdataStore db.Db, accountService remote.AccountService, replaceSeparatorFunc func(string) string) (*MenuHandlers, error) {
func NewMenuHandlers(appFlags *FlagManager, userdataStore db.Db, logdb db.Db, accountService remote.AccountService, replaceSeparatorFunc func(string) string) (*MenuHandlers, error) {
if userdataStore == nil {
return nil, fmt.Errorf("cannot create handler with nil userdata store")
}
userDb := &store.UserDataStore{
Db: userdataStore,
}
smsservice := sms.SmsService{
Accountservice: accountService,
Userdatastore: *userDb,
}
logDb := store.LogDb{
Db: logdb,
}
// Instantiate the SubPrefixDb with "DATATYPE_USERDATA" prefix
prefix := storedb.ToBytes(db.DATATYPE_USERDATA)
@ -98,7 +109,9 @@ func NewMenuHandlers(appFlags *FlagManager, userdataStore db.Db, accountService
userdataStore: userDb,
flagManager: appFlags,
accountService: accountService,
smsService: smsservice,
prefixDb: prefixDb,
logDb: logDb,
profile: &profile.Profile{Max: 6},
ReplaceSeparatorFunc: replaceSeparatorFunc,
}
@ -186,10 +199,16 @@ func (h *MenuHandlers) SetLanguage(ctx context.Context, sym string, input []byte
// handles the account creation when no existing account is present for the session and stores associated data in the user data store.
func (h *MenuHandlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error {
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
flag_account_creation_failed, _ := h.flagManager.GetFlag("flag_account_creation_failed")
r, err := h.accountService.CreateAccount(ctx)
if err != nil {
return err
res.FlagSet = append(res.FlagSet, flag_account_creation_failed)
logg.ErrorCtxf(ctx, "failed to create an account", "error", err)
return nil
}
res.FlagReset = append(res.FlagReset, flag_account_creation_failed)
trackingId := r.TrackingId
publicKey := r.PublicKey
@ -199,11 +218,16 @@ func (h *MenuHandlers) createAccountNoExist(ctx context.Context, sessionId strin
storedb.DATA_ACCOUNT_ALIAS: "",
}
store := h.userdataStore
logdb := h.logDb
for key, value := range data {
err = store.WriteEntry(ctx, sessionId, key, []byte(value))
if err != nil {
return err
}
err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write log entry", "key", key, "value", value)
}
}
publicKeyNormalized, err := hex.NormalizeHex(publicKey)
if err != nil {
@ -213,6 +237,12 @@ func (h *MenuHandlers) createAccountNoExist(ctx context.Context, sessionId strin
if err != nil {
return err
}
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write log entry", "key", storedb.DATA_PUBLIC_KEY_REVERSE, "value", sessionId)
}
res.FlagSet = append(res.FlagSet, flag_account_created)
return nil
}
@ -272,26 +302,31 @@ func (h *MenuHandlers) CheckAccountCreated(ctx context.Context, sym string, inpu
return res, nil
}
// ResetValidPin resets the flag_valid_pin flag.
func (h *MenuHandlers) ResetValidPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
res.FlagReset = append(res.FlagReset, flag_valid_pin)
return res, nil
}
// CheckBlockedStatus resets the account blocked flag if the PIN attempts have been reset by an admin.
// CheckBlockedStatus:
// 1. Checks whether the DATA_SELF_PIN_RESET is 1 and sets the flag_account_pin_reset
// 2. resets the account blocked flag if the PIN attempts have been reset by an admin.
func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
store := h.userdataStore
flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked")
flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset")
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
res.FlagReset = append(res.FlagReset, flag_account_pin_reset)
selfPinReset, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET)
if err == nil {
pinResetValue, _ := strconv.ParseUint(string(selfPinReset), 0, 64)
if pinResetValue == 1 {
res.FlagSet = append(res.FlagSet, flag_account_pin_reset)
}
}
currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS)
if err != nil {
if !db.IsNotFound(err) {
@ -300,7 +335,6 @@ func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input
}
pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64)
if pinAttemptsValue == 0 {
res.FlagReset = append(res.FlagReset, flag_account_blocked)
return res, nil
@ -343,29 +377,6 @@ func (h *MenuHandlers) ResetIncorrectPin(ctx context.Context, sym string, input
return res, nil
}
// VerifyNewPin checks if a new PIN meets the required format criteria.
func (h *MenuHandlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
_, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
if string(input) != "0" {
pinInput := string(input)
// Validate that the PIN is a 4-digit number.
if pin.IsValidPIN(pinInput) {
res.FlagSet = append(res.FlagSet, flag_valid_pin)
} else {
res.FlagReset = append(res.FlagReset, flag_valid_pin)
}
} else {
res.FlagSet = append(res.FlagSet, flag_valid_pin)
}
return res, nil
}
// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_VALUE,
// during the account creation process
// and during the change PIN process.
@ -378,15 +389,20 @@ func (h *MenuHandlers) SaveTemporaryPin(ctx context.Context, sym string, input [
return res, fmt.Errorf("missing session")
}
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
accountPIN := string(input)
flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin")
// Validate that the PIN is a 4-digit number.
if !pin.IsValidPIN(accountPIN) {
res.FlagSet = append(res.FlagSet, flag_incorrect_pin)
if string(input) == "0" {
return res, nil
}
res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
accountPIN := string(input)
// Validate that the PIN has a valid format.
if !pin.IsValidPIN(accountPIN) {
res.FlagSet = append(res.FlagSet, flag_invalid_pin)
return res, nil
}
res.FlagReset = append(res.FlagReset, flag_invalid_pin)
// Hash the PIN
hashedPIN, err := pin.HashPIN(string(accountPIN))
@ -396,93 +412,19 @@ func (h *MenuHandlers) SaveTemporaryPin(ctx context.Context, sym string, input [
}
store := h.userdataStore
logdb := h.logDb
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryAccountPIN entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err)
return res, err
}
return res, nil
}
// SaveOthersTemporaryPin allows authorized users to set temporary PINs for blocked numbers.
func (h *MenuHandlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
store := h.userdataStore
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
temporaryPin := string(input)
// Validate that the input is a 4-digit number.
if !pin.IsValidPIN(temporaryPin) {
return res, nil
}
// Retrieve the blocked number associated with this session
blockedNumber, err := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER)
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
if err != nil {
logg.ErrorCtxf(ctx, "failed to read blockedNumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err)
return res, err
logg.DebugCtxf(ctx, "Failed to write temporaryAccountPIN log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err)
}
// Hash the temporary PIN
hashedPIN, err := pin.HashPIN(string(temporaryPin))
if err != nil {
logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err)
return res, err
}
// Save the hashed temporary PIN for that blocked number
err = store.WriteEntry(ctx, string(blockedNumber), storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write hashed temporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryPin, "error", err)
return res, err
}
return res, nil
}
// CheckBlockedNumPinMisMatch checks if the provided PIN matches a temporary PIN stored for a blocked number.
func (h *MenuHandlers) CheckBlockedNumPinMisMatch(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
if string(input) == "0" {
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
return res, nil
}
// Get blocked number from storage.
store := h.userdataStore
blockedNumber, err := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read blockedNumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err)
return res, err
}
// Get Hashed temporary PIN for the blocked number.
hashedTemporaryPin, err := store.ReadEntry(ctx, string(blockedNumber), storedb.DATA_TEMPORARY_VALUE)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err)
return res, err
}
if len(hashedTemporaryPin) == 0 {
logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE)
return res, fmt.Errorf("Data error encountered")
}
if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) {
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
} else {
res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
}
return res, nil
}
@ -494,6 +436,14 @@ func (h *MenuHandlers) ResetInvalidPIN(ctx context.Context, sym string, input []
return res, nil
}
// ResetApiCallFailure resets the api call failure flag
func (h *MenuHandlers) ResetApiCallFailure(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
res.FlagReset = append(res.FlagReset, flag_api_error)
return res, nil
}
// ConfirmPinChange validates user's new PIN. If input matches the temporary PIN, saves it as the new account PIN.
func (h *MenuHandlers) ConfirmPinChange(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
@ -502,6 +452,7 @@ func (h *MenuHandlers) ConfirmPinChange(ctx context.Context, sym string, input [
return res, fmt.Errorf("missing session")
}
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset")
if string(input) == "0" {
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
@ -509,6 +460,7 @@ func (h *MenuHandlers) ConfirmPinChange(ctx context.Context, sym string, input [
}
store := h.userdataStore
logdb := h.logDb
hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err)
@ -533,16 +485,85 @@ func (h *MenuHandlers) ConfirmPinChange(ctx context.Context, sym string, input [
return res, err
}
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write AccountPIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err)
}
// set the DATA_SELF_PIN_RESET as 0
err = store.WriteEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET, []byte("0"))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write DATA_SELF_PIN_RESET entry with", "key", storedb.DATA_SELF_PIN_RESET, "self PIN reset value", "0", "error", err)
return res, err
}
res.FlagReset = append(res.FlagReset, flag_account_pin_reset)
return res, nil
}
// ValidateBlockedNumber performs validation of phone numbers during the Reset other's PIN.
// It checks phone number format and verifies registration status.
// If valid, it writes the number under DATA_BLOCKED_NUMBER on the admin account
func (h *MenuHandlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number")
store := h.userdataStore
logdb := h.logDb
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
if string(input) == "0" {
res.FlagReset = append(res.FlagReset, flag_unregistered_number)
return res, nil
}
blockedNumber := string(input)
formattedNumber, err := phone.FormatPhoneNumber(blockedNumber)
if err != nil {
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", blockedNumber, "error", err)
return res, nil
}
_, err = store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY)
if err != nil {
if db.IsNotFound(err) {
logg.InfoCtxf(ctx, "Invalid or unregistered number")
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
return res, nil
} else {
logg.ErrorCtxf(ctx, "Error on ValidateBlockedNumber", "error", err)
return res, err
}
}
err = store.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber))
if err != nil {
return res, nil
}
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write blocked number log entry", "key", storedb.DATA_BLOCKED_NUMBER, "value", formattedNumber, "error", err)
}
return res, nil
}
// ResetOthersPin handles the PIN reset process for other users' accounts by:
// 1. Retrieving the blocked phone number from the session
// 2. Fetching the hashed temporary PIN associated with that number
// 3. Updating the account PIN with the temporary PIN
// 2. Writing the DATA_SELF_PIN_RESET on the blocked phone number
// 3. Resetting the DATA_INCORRECT_PIN_ATTEMPTS to 0 for the blocked phone number
func (h *MenuHandlers) ResetOthersPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
store := h.userdataStore
smsservice := h.smsService
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
@ -552,19 +573,11 @@ func (h *MenuHandlers) ResetOthersPin(ctx context.Context, sym string, input []b
logg.ErrorCtxf(ctx, "failed to read blockedPhonenumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err)
return res, err
}
hashedTemporaryPin, err := store.ReadEntry(ctx, string(blockedPhonenumber), storedb.DATA_TEMPORARY_VALUE)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read hashedTmporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err)
return res, err
}
if len(hashedTemporaryPin) == 0 {
logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE)
return res, fmt.Errorf("Data error encountered")
}
err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
// set the DATA_SELF_PIN_RESET for the account
err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_SELF_PIN_RESET, []byte("1"))
if err != nil {
return res, err
return res, nil
}
err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0")))
@ -572,7 +585,15 @@ func (h *MenuHandlers) ResetOthersPin(ctx context.Context, sym string, input []b
logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err)
return res, err
}
blockedPhoneStr := string(blockedPhonenumber)
//Trigger an SMS to inform a user that the blocked account has been reset
if phone.IsValidPhoneNumber(blockedPhoneStr) {
err = smsservice.SendPINResetSMS(ctx, sessionId, blockedPhoneStr)
if err != nil {
logg.DebugCtxf(ctx, "Failed to send PIN reset SMS", "error", err)
return res, nil
}
}
return res, nil
}
@ -635,65 +656,29 @@ func (h *MenuHandlers) ResetUnregisteredNumber(ctx context.Context, sym string,
return res, nil
}
// ValidateBlockedNumber performs validation of phone numbers, specifically for blocked numbers in the system.
// It checks phone number format and verifies registration status.
func (h *MenuHandlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number")
store := h.userdataStore
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
if string(input) == "0" {
res.FlagReset = append(res.FlagReset, flag_unregistered_number)
return res, nil
}
blockedNumber := string(input)
formattedNumber, err := phone.FormatPhoneNumber(blockedNumber)
if err != nil {
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", blockedNumber, "error", err)
return res, nil
}
_, err = store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY)
if err != nil {
if db.IsNotFound(err) {
logg.InfoCtxf(ctx, "Invalid or unregistered number")
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
return res, nil
} else {
logg.ErrorCtxf(ctx, "Error on ValidateBlockedNumber", "error", err)
return res, err
}
}
err = store.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber))
if err != nil {
return res, nil
}
return res, nil
}
// VerifyCreatePin checks whether the confirmation PIN is similar to the temporary PIN
// If similar, it sets the USERFLAG_PIN_SET flag and writes the account PIN allowing the user
// to access the main menu.
func (h *MenuHandlers) VerifyCreatePin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set")
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set")
if string(input) == "0" {
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
return res, nil
}
store := h.userdataStore
logdb := h.logDb
hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err)
@ -705,20 +690,26 @@ func (h *MenuHandlers) VerifyCreatePin(ctx context.Context, sym string, input []
}
if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) {
res.FlagSet = []uint32{flag_valid_pin}
res.FlagReset = []uint32{flag_pin_mismatch}
res.FlagSet = append(res.FlagSet, flag_valid_pin)
res.FlagSet = append(res.FlagSet, flag_pin_set)
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
} else {
res.FlagSet = []uint32{flag_pin_mismatch}
res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
return res, nil
}
// save the hashed PIN as the new account PIN
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err)
return res, err
}
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write DATA_ACCOUNT_PIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err)
}
return res, nil
}
@ -731,7 +722,10 @@ func (h *MenuHandlers) SaveFirstname(ctx context.Context, sym string, input []by
return res, fmt.Errorf("missing session")
}
firstName := string(input)
store := h.userdataStore
logdb := h.logDb
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set")
@ -749,6 +743,11 @@ func (h *MenuHandlers) SaveFirstname(ctx context.Context, sym string, input []by
return res, err
}
res.FlagSet = append(res.FlagSet, flag_firstname_set)
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryFirstName))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FIRST_NAME, "value", temporaryFirstName)
}
} else {
if firstNameSet {
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName))
@ -774,6 +773,7 @@ func (h *MenuHandlers) SaveFamilyname(ctx context.Context, sym string, input []b
}
store := h.userdataStore
logdb := h.logDb
familyName := string(input)
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
@ -793,6 +793,11 @@ func (h *MenuHandlers) SaveFamilyname(ctx context.Context, sym string, input []b
return res, err
}
res.FlagSet = append(res.FlagSet, flag_familyname_set)
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME, []byte(temporaryFamilyName))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FAMILY_NAME, "value", temporaryFamilyName)
}
} else {
if familyNameSet {
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName))
@ -849,6 +854,8 @@ func (h *MenuHandlers) SaveYob(ctx context.Context, sym string, input []byte) (r
}
yob := string(input)
store := h.userdataStore
logdb := h.logDb
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set")
@ -867,6 +874,11 @@ func (h *MenuHandlers) SaveYob(ctx context.Context, sym string, input []byte) (r
return res, err
}
res.FlagSet = append(res.FlagSet, flag_yob_set)
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_YOB, []byte(temporaryYob))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write yob db log entry", "key", storedb.DATA_YOB, "value", temporaryYob)
}
} else {
if yobSet {
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob))
@ -892,6 +904,7 @@ func (h *MenuHandlers) SaveLocation(ctx context.Context, sym string, input []byt
}
location := string(input)
store := h.userdataStore
logdb := h.logDb
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_location_set, _ := h.flagManager.GetFlag("flag_location_set")
@ -910,6 +923,11 @@ func (h *MenuHandlers) SaveLocation(ctx context.Context, sym string, input []byt
return res, err
}
res.FlagSet = append(res.FlagSet, flag_location_set)
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_LOCATION, []byte(temporaryLocation))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write location db log entry", "key", storedb.DATA_LOCATION, "value", temporaryLocation)
}
} else {
if locationSet {
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location))
@ -937,6 +955,7 @@ func (h *MenuHandlers) SaveGender(ctx context.Context, sym string, input []byte)
}
gender := strings.Split(symbol, "_")[1]
store := h.userdataStore
logdb := h.logDb
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set")
@ -955,6 +974,12 @@ func (h *MenuHandlers) SaveGender(ctx context.Context, sym string, input []byte)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_gender_set)
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_GENDER, []byte(temporaryGender))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write gender db log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryGender)
}
} else {
if genderSet {
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(gender))
@ -981,6 +1006,7 @@ func (h *MenuHandlers) SaveOfferings(ctx context.Context, sym string, input []by
offerings := string(input)
store := h.userdataStore
logdb := h.logDb
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set")
@ -1000,6 +1026,11 @@ func (h *MenuHandlers) SaveOfferings(ctx context.Context, sym string, input []by
return res, err
}
res.FlagSet = append(res.FlagSet, flag_offerings_set)
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryOfferings))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write offerings db log entry", "key", storedb.DATA_OFFERINGS, "value", offerings)
}
} else {
if offeringsSet {
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings))
@ -1299,17 +1330,30 @@ func (h *MenuHandlers) ResetAccountAuthorized(ctx context.Context, sym string, i
return res, nil
}
// CheckIdentifier retrieves the PublicKey from the JSON data file.
// CheckIdentifier retrieves the Public key from the userdatastore under the key: DATA_PUBLIC_KEY and triggers an sms that
// will be sent to the associated session id
func (h *MenuHandlers) CheckIdentifier(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
smsservice := h.smsService
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
store := h.userdataStore
publicKey, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
return res, err
}
res.Content = string(publicKey)
//trigger an address sms to be delivered to the associated session id
err = smsservice.SendAddressSMS(ctx)
if err != nil {
logg.DebugCtxf(ctx, "Failed to trigger an address sms", "error", err)
return res, nil
}
return res, nil
}
@ -1410,7 +1454,7 @@ func (h *MenuHandlers) CheckAccountStatus(ctx context.Context, sym string, input
if err != nil {
res.FlagSet = append(res.FlagSet, flag_api_error)
logg.ErrorCtxf(ctx, "failed on TrackAccountStatus", "error", err)
return res, err
return res, nil
}
res.FlagReset = append(res.FlagReset, flag_api_error)
@ -1576,6 +1620,7 @@ func (h *MenuHandlers) ValidateRecipient(ctx context.Context, sym string, input
}
flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
recipient := string(input)
@ -1649,10 +1694,13 @@ func (h *MenuHandlers) ValidateRecipient(ctx context.Context, sym string, input
logg.InfoCtxf(ctx, "Resolving with fqdn alias", "alias", fqdn)
AliasAddress, err = h.accountService.CheckAliasAddress(ctx, fqdn)
if err == nil {
res.FlagReset = append(res.FlagReset, flag_api_error)
AliasAddressResult = AliasAddress.Address
continue
} else {
res.FlagSet = append(res.FlagSet, flag_api_error)
logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err)
return res, nil
}
}
}
@ -1706,6 +1754,7 @@ func (h *MenuHandlers) TransactionReset(ctx context.Context, sym string, input [
func (h *MenuHandlers) InviteValidRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
store := h.userdataStore
smsservice := h.smsService
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
@ -1716,18 +1765,23 @@ func (h *MenuHandlers) InviteValidRecipient(ctx context.Context, sym string, inp
l := gotext.NewLocale(translationDir, code)
l.AddDomain("default")
recipient, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
if len(recipient) == 0 {
logg.ErrorCtxf(ctx, "recipient is empty", "key", storedb.DATA_TEMPORARY_VALUE)
return res, fmt.Errorf("Data error encountered")
recipient, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read invalid recipient info", "error", err)
return res, err
}
// TODO
// send an invitation SMS
// if successful
// res.Content = l.Get("Your invitation to %s to join Sarafu Network has been sent.", string(recipient))
if !phone.IsValidPhoneNumber(string(recipient)) {
logg.InfoCtxf(ctx, "corrupted recipient", "key", storedb.DATA_TEMPORARY_VALUE, "recipient", recipient)
return res, nil
}
res.Content = l.Get("Your invite request for %s to Sarafu Network failed. Please try again later.", string(recipient))
_, err = smsservice.Accountservice.SendUpsellSMS(ctx, sessionId, string(recipient))
if err != nil {
res.Content = l.Get("Your invite request for %s to Sarafu Network failed. Please try again later.", string(recipient))
return res, nil
}
res.Content = l.Get("Your invitation to %s to join Sarafu Network has been sent.", string(recipient))
return res, nil
}
@ -1960,6 +2014,7 @@ func (h *MenuHandlers) InitiateTransaction(ctx context.Context, sym string, inpu
func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
userStore := h.userdataStore
logdb := h.logDb
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
@ -1967,6 +2022,7 @@ func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []b
}
flag_no_active_voucher, _ := h.flagManager.GetFlag("flag_no_active_voucher")
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
if err != nil {
@ -1977,9 +2033,10 @@ func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []b
// Fetch vouchers from API
vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
if err != nil {
res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
res.FlagSet = append(res.FlagSet, flag_api_error)
return res, nil
}
res.FlagReset = append(res.FlagReset, flag_api_error)
if len(vouchersResp) == 0 {
res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
@ -2014,6 +2071,10 @@ func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []b
logg.ErrorCtxf(ctx, "Failed to write active voucher data", "key", key, "error", err)
return res, err
}
err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write voucher db log entry", "key", key, "value", value)
}
}
logg.InfoCtxf(ctx, "Default voucher set", "symbol", defaultSym, "balance", defaultBal, "decimals", defaultDec, "address", defaultAddr)
@ -2190,6 +2251,7 @@ func (h *MenuHandlers) GetVoucherDetails(ctx context.Context, sym string, input
res.FlagSet = append(res.FlagSet, flag_api_error)
return res, nil
}
res.FlagReset = append(res.FlagReset, flag_api_error)
res.Content = fmt.Sprintf(
"Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", voucherData.TokenName, voucherData.TokenSymbol, voucherData.TokenCommodity, voucherData.TokenLocation,
@ -2210,6 +2272,7 @@ func (h *MenuHandlers) CheckTransactions(ctx context.Context, sym string, input
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
userStore := h.userdataStore
logdb := h.logDb
publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
@ -2223,6 +2286,7 @@ func (h *MenuHandlers) CheckTransactions(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err)
return res, err
}
res.FlagReset = append(res.FlagReset, flag_api_error)
// Return if there are no transactions
if len(transactionsResp) == 0 {
@ -2249,6 +2313,10 @@ func (h *MenuHandlers) CheckTransactions(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err)
return res, err
}
err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write tx db log entry", "key", key, "value", value)
}
}
res.FlagReset = append(res.FlagReset, flag_no_transfers)
@ -2460,6 +2528,8 @@ func (h *MenuHandlers) RequestCustomAlias(ctx context.Context, sym string, input
return res, nil
}
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
store := h.userdataStore
aliasHint, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
if err != nil {
@ -2483,9 +2553,12 @@ func (h *MenuHandlers) RequestCustomAlias(ctx context.Context, sym string, input
sanitizedInput := sanitizeAliasHint(string(input))
aliasResult, err := h.accountService.RequestAlias(ctx, string(pubKey), sanitizedInput)
if err != nil {
res.FlagSet = append(res.FlagSet, flag_api_error)
logg.ErrorCtxf(ctx, "failed to retrieve alias", "alias", string(aliasHint), "error_alias_request", err)
return res, fmt.Errorf("Failed to retrieve alias: %s", err.Error())
return res, nil
}
res.FlagReset = append(res.FlagReset, flag_api_error)
alias := aliasResult.Alias
logg.InfoCtxf(ctx, "Suggested alias ", "alias", alias)
@ -2531,6 +2604,7 @@ func (h *MenuHandlers) GetSuggestedAlias(ctx context.Context, sym string, input
func (h *MenuHandlers) ConfirmNewAlias(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
store := h.userdataStore
logdb := h.logDb
flag_alias_set, _ := h.flagManager.GetFlag("flag_alias_set")
@ -2549,6 +2623,11 @@ func (h *MenuHandlers) ConfirmNewAlias(ctx context.Context, sym string, input []
return res, err
}
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(newAlias))
if err != nil {
logg.DebugCtxf(ctx, "Failed to write account alias db log entry", "key", storedb.DATA_ACCOUNT_ALIAS, "value", newAlias)
}
res.FlagSet = append(res.FlagSet, flag_alias_set)
return res, nil
}

View File

@ -62,6 +62,25 @@ func InitializeTestStore(t *testing.T) (context.Context, *store.UserDataStore) {
return ctx, store
}
// InitializeTestLogdbStore sets up and returns an in-memory database and logdb store.
func InitializeTestLogdbStore(t *testing.T) (context.Context, *store.UserDataStore) {
ctx := context.Background()
// Initialize memDb
db := memdb.NewMemDb()
err := db.Connect(ctx, "")
require.NoError(t, err, "Failed to connect to memDb")
// Create UserDataStore with memDb
logdb := &store.UserDataStore{Db: db}
t.Cleanup(func() {
db.Close(ctx) // Ensure the DB is closed after each test
})
return ctx, logdb
}
func InitializeTestSubPrefixDb(t *testing.T, ctx context.Context) *storedb.SubPrefixDb {
db := memdb.NewMemDb()
err := db.Connect(ctx, "")
@ -76,6 +95,7 @@ func InitializeTestSubPrefixDb(t *testing.T, ctx context.Context) *storedb.SubPr
func TestNewMenuHandlers(t *testing.T) {
_, store := InitializeTestStore(t)
_, logdb := InitializeTestLogdbStore(t)
fm, err := NewFlagManager(flagsPath)
if err != nil {
@ -86,7 +106,7 @@ func TestNewMenuHandlers(t *testing.T) {
// Test case for valid UserDataStore
t.Run("Valid UserDataStore", func(t *testing.T) {
handlers, err := NewMenuHandlers(fm, store, &accountService, mockReplaceSeparator)
handlers, err := NewMenuHandlers(fm, store, logdb, &accountService, mockReplaceSeparator)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@ -110,7 +130,7 @@ func TestNewMenuHandlers(t *testing.T) {
// Test case for nil UserDataStore
t.Run("Nil UserDataStore", func(t *testing.T) {
handlers, err := NewMenuHandlers(fm, nil, &accountService, mockReplaceSeparator)
handlers, err := NewMenuHandlers(fm, nil, logdb, &accountService, mockReplaceSeparator)
if err == nil {
t.Fatal("expected an error, got none")
}
@ -192,8 +212,13 @@ func TestInit(t *testing.T) {
func TestCreateAccount(t *testing.T) {
sessionId := "session123"
ctx, store := InitializeTestStore(t)
ctx, userStore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
_, logdb := InitializeTestLogdbStore(t)
logDb := store.LogDb{
Db: logdb,
}
fm, err := NewFlagManager(flagsPath)
if err != nil {
@ -201,6 +226,8 @@ func TestCreateAccount(t *testing.T) {
}
flag_account_created, err := fm.GetFlag("flag_account_created")
flag_account_creation_failed, _ := fm.GetFlag("flag_account_creation_failed")
if err != nil {
t.Logf(err.Error())
}
@ -217,7 +244,8 @@ func TestCreateAccount(t *testing.T) {
PublicKey: "0xD3adB33f",
},
expectedResult: resource.Result{
FlagSet: []uint32{flag_account_created},
FlagSet: []uint32{flag_account_created},
FlagReset: []uint32{flag_account_creation_failed},
},
},
}
@ -226,8 +254,9 @@ func TestCreateAccount(t *testing.T) {
mockAccountService := new(mocks.MockAccountService)
h := &MenuHandlers{
userdataStore: store,
userdataStore: userStore,
accountService: mockAccountService,
logDb: logDb,
flagManager: fm,
}
@ -265,8 +294,13 @@ func TestWithPersister_PanicWhenAlreadySet(t *testing.T) {
func TestSaveFirstname(t *testing.T) {
sessionId := "session123"
ctx, store := InitializeTestStore(t)
ctx, userStore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
_, logdb := InitializeTestLogdbStore(t)
logDb := store.LogDb{
Db: logdb,
}
fm, _ := NewFlagManager(flagsPath)
@ -282,7 +316,7 @@ func TestSaveFirstname(t *testing.T) {
// Define test data
firstName := "John"
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil {
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil {
t.Fatal(err)
}
@ -290,9 +324,10 @@ func TestSaveFirstname(t *testing.T) {
// Create the MenuHandlers instance with the mock store
h := &MenuHandlers{
userdataStore: store,
userdataStore: userStore,
flagManager: fm,
st: mockState,
logDb: logDb,
}
// Call the method
@ -303,14 +338,19 @@ func TestSaveFirstname(t *testing.T) {
assert.Equal(t, expectedResult, res)
// Verify that the DATA_FIRST_NAME entry has been updated with the temporary value
storedFirstName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME)
storedFirstName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME)
assert.Equal(t, firstName, string(storedFirstName))
}
func TestSaveFamilyname(t *testing.T) {
sessionId := "session123"
ctx, store := InitializeTestStore(t)
ctx, userStore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
_, logdb := InitializeTestLogdbStore(t)
logDb := store.LogDb{
Db: logdb,
}
fm, _ := NewFlagManager(flagsPath)
@ -328,15 +368,16 @@ func TestSaveFamilyname(t *testing.T) {
// Define test data
familyName := "Doeee"
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil {
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil {
t.Fatal(err)
}
// Create the MenuHandlers instance with the mock store
h := &MenuHandlers{
userdataStore: store,
userdataStore: userStore,
st: mockState,
flagManager: fm,
logDb: logDb,
}
// Call the method
@ -347,14 +388,19 @@ func TestSaveFamilyname(t *testing.T) {
assert.Equal(t, expectedResult, res)
// Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value
storedFamilyName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME)
storedFamilyName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME)
assert.Equal(t, familyName, string(storedFamilyName))
}
func TestSaveYoB(t *testing.T) {
sessionId := "session123"
ctx, store := InitializeTestStore(t)
ctx, userStore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
_, logdb := InitializeTestLogdbStore(t)
logDb := store.LogDb{
Db: logdb,
}
fm, _ := NewFlagManager(flagsPath)
@ -370,7 +416,7 @@ func TestSaveYoB(t *testing.T) {
// Define test data
yob := "1980"
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil {
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil {
t.Fatal(err)
}
@ -378,9 +424,10 @@ func TestSaveYoB(t *testing.T) {
// Create the MenuHandlers instance with the mock store
h := &MenuHandlers{
userdataStore: store,
userdataStore: userStore,
flagManager: fm,
st: mockState,
logDb: logDb,
}
// Call the method
@ -391,14 +438,19 @@ func TestSaveYoB(t *testing.T) {
assert.Equal(t, expectedResult, res)
// Verify that the DATA_YOB entry has been updated with the temporary value
storedYob, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_YOB)
storedYob, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_YOB)
assert.Equal(t, yob, string(storedYob))
}
func TestSaveLocation(t *testing.T) {
sessionId := "session123"
ctx, store := InitializeTestStore(t)
ctx, userStore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
_, logdb := InitializeTestLogdbStore(t)
logDb := store.LogDb{
Db: logdb,
}
fm, _ := NewFlagManager(flagsPath)
@ -414,7 +466,7 @@ func TestSaveLocation(t *testing.T) {
// Define test data
location := "Kilifi"
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location)); err != nil {
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location)); err != nil {
t.Fatal(err)
}
@ -422,9 +474,10 @@ func TestSaveLocation(t *testing.T) {
// Create the MenuHandlers instance with the mock store
h := &MenuHandlers{
userdataStore: store,
userdataStore: userStore,
flagManager: fm,
st: mockState,
logDb: logDb,
}
// Call the method
@ -435,14 +488,19 @@ func TestSaveLocation(t *testing.T) {
assert.Equal(t, expectedResult, res)
// Verify that the DATA_LOCATION entry has been updated with the temporary value
storedLocation, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION)
storedLocation, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION)
assert.Equal(t, location, string(storedLocation))
}
func TestSaveOfferings(t *testing.T) {
sessionId := "session123"
ctx, store := InitializeTestStore(t)
ctx, userStore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
_, logdb := InitializeTestLogdbStore(t)
logDb := store.LogDb{
Db: logdb,
}
fm, _ := NewFlagManager(flagsPath)
@ -458,7 +516,7 @@ func TestSaveOfferings(t *testing.T) {
// Define test data
offerings := "Bananas"
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil {
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil {
t.Fatal(err)
}
@ -466,9 +524,10 @@ func TestSaveOfferings(t *testing.T) {
// Create the MenuHandlers instance with the mock store
h := &MenuHandlers{
userdataStore: store,
userdataStore: userStore,
flagManager: fm,
st: mockState,
logDb: logDb,
}
// Call the method
@ -479,14 +538,19 @@ func TestSaveOfferings(t *testing.T) {
assert.Equal(t, expectedResult, res)
// Verify that the DATA_OFFERINGS entry has been updated with the temporary value
storedOfferings, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS)
storedOfferings, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS)
assert.Equal(t, offerings, string(storedOfferings))
}
func TestSaveGender(t *testing.T) {
sessionId := "session123"
ctx, store := InitializeTestStore(t)
ctx, userStore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
_, logdb := InitializeTestLogdbStore(t)
logDb := store.LogDb{
Db: logdb,
}
fm, _ := NewFlagManager(flagsPath)
@ -526,16 +590,17 @@ func TestSaveGender(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil {
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil {
t.Fatal(err)
}
mockState.ExecPath = append(mockState.ExecPath, tt.executingSymbol)
// Create the MenuHandlers instance with the mock store
h := &MenuHandlers{
userdataStore: store,
userdataStore: userStore,
st: mockState,
flagManager: fm,
logDb: logDb,
}
expectedResult := resource.Result{}
@ -550,7 +615,7 @@ func TestSaveGender(t *testing.T) {
assert.Equal(t, expectedResult, res)
// Verify that the DATA_GENDER entry has been updated with the temporary value
storedGender, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_GENDER)
storedGender, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_GENDER)
assert.Equal(t, tt.expectedGender, string(storedGender))
})
}
@ -558,20 +623,27 @@ func TestSaveGender(t *testing.T) {
func TestSaveTemporaryPin(t *testing.T) {
sessionId := "session123"
ctx, store := InitializeTestStore(t)
ctx, userdatastore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
_, logdb := InitializeTestLogdbStore(t)
logDb := store.LogDb{
Db: logdb,
}
fm, err := NewFlagManager(flagsPath)
if err != nil {
log.Fatal(err)
}
flag_incorrect_pin, _ := fm.GetFlag("flag_incorrect_pin")
flag_invalid_pin, _ := fm.GetFlag("flag_invalid_pin")
// Create the MenuHandlers instance with the mock flag manager
h := &MenuHandlers{
flagManager: fm,
userdataStore: store,
userdataStore: userdatastore,
logDb: logDb,
}
// Define test cases
@ -584,14 +656,14 @@ func TestSaveTemporaryPin(t *testing.T) {
name: "Valid Pin entry",
input: []byte("1234"),
expectedResult: resource.Result{
FlagReset: []uint32{flag_incorrect_pin},
FlagReset: []uint32{flag_invalid_pin},
},
},
{
name: "Invalid Pin entry",
input: []byte("12343"),
expectedResult: resource.Result{
FlagSet: []uint32{flag_incorrect_pin},
FlagSet: []uint32{flag_invalid_pin},
},
},
}
@ -612,9 +684,15 @@ func TestSaveTemporaryPin(t *testing.T) {
func TestCheckIdentifier(t *testing.T) {
sessionId := "session123"
ctx, store := InitializeTestStore(t)
ctx, userdatastore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
_, logdb := InitializeTestLogdbStore(t)
logDb := store.LogDb{
Db: logdb,
}
// Define test cases
tests := []struct {
name string
@ -634,14 +712,15 @@ func TestCheckIdentifier(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(tt.publicKey))
err := userdatastore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(tt.publicKey))
if err != nil {
t.Fatal(err)
}
// Create the MenuHandlers instance with the mock store
h := &MenuHandlers{
userdataStore: store,
userdataStore: userdatastore,
logDb: logDb,
}
// Call the method
@ -1843,53 +1922,7 @@ func TestGetProfile(t *testing.T) {
}
}
func TestVerifyNewPin(t *testing.T) {
sessionId := "session123"
fm, _ := NewFlagManager(flagsPath)
mockState := state.NewState(16)
flag_valid_pin, _ := fm.GetFlag("flag_valid_pin")
mockAccountService := new(mocks.MockAccountService)
h := &MenuHandlers{
flagManager: fm,
accountService: mockAccountService,
st: mockState,
}
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
tests := []struct {
name string
input []byte
expectedResult resource.Result
}{
{
name: "Test with valid pin",
input: []byte("1234"),
expectedResult: resource.Result{
FlagSet: []uint32{flag_valid_pin},
},
},
{
name: "Test with invalid pin",
input: []byte("123"),
expectedResult: resource.Result{
FlagReset: []uint32{flag_valid_pin},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
//Call the function under test
res, _ := h.VerifyNewPin(ctx, "verify_new_pin", tt.input)
//Assert that the result set to content is what was expected
assert.Equal(t, res, tt.expectedResult, "Result should contain flags set according to user input")
})
}
}
func TestConfirmPin(t *testing.T) {
func TestConfirmPinChange(t *testing.T) {
sessionId := "session123"
mockState := state.NewState(16)
@ -1898,6 +1931,8 @@ func TestConfirmPin(t *testing.T) {
fm, _ := NewFlagManager(flagsPath)
flag_pin_mismatch, _ := fm.GetFlag("flag_pin_mismatch")
flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset")
mockAccountService := new(mocks.MockAccountService)
h := &MenuHandlers{
userdataStore: store,
@ -1917,7 +1952,7 @@ func TestConfirmPin(t *testing.T) {
input: []byte("1234"),
temporarypin: "1234",
expectedResult: resource.Result{
FlagReset: []uint32{flag_pin_mismatch},
FlagReset: []uint32{flag_pin_mismatch, flag_account_pin_reset},
},
},
}
@ -1994,8 +2029,13 @@ func TestManageVouchers(t *testing.T) {
sessionId := "session123"
publicKey := "0X13242618721"
ctx, store := InitializeTestStore(t)
ctx, userStore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
_, logdb := InitializeTestLogdbStore(t)
logDb := store.LogDb{
Db: logdb,
}
fm, err := NewFlagManager(flagsPath)
if err != nil {
@ -2005,8 +2045,13 @@ func TestManageVouchers(t *testing.T) {
if err != nil {
t.Fatal(err)
}
flag_api_error, err := fm.GetFlag("flag_api_call_error")
err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil {
t.Fatal(err)
}
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil {
t.Fatal(err)
}
@ -2025,7 +2070,8 @@ func TestManageVouchers(t *testing.T) {
expectedVoucherSymbols: []byte(""),
expectedUpdatedAddress: []byte(""),
expectedResult: resource.Result{
FlagSet: []uint32{flag_no_active_voucher},
FlagSet: []uint32{flag_no_active_voucher},
FlagReset: []uint32{flag_api_error},
},
},
{
@ -2041,7 +2087,7 @@ func TestManageVouchers(t *testing.T) {
expectedVoucherSymbols: []byte("1:TOKEN1"),
expectedUpdatedAddress: []byte("0x123"),
expectedResult: resource.Result{
FlagReset: []uint32{flag_no_active_voucher},
FlagReset: []uint32{flag_api_error, flag_no_active_voucher},
},
},
{
@ -2054,7 +2100,7 @@ func TestManageVouchers(t *testing.T) {
expectedVoucherSymbols: []byte("1:SRF\n2:MILO"),
expectedUpdatedAddress: []byte("0xd4c288865Ce"),
expectedResult: resource.Result{
FlagReset: []uint32{flag_no_active_voucher},
FlagReset: []uint32{flag_api_error, flag_no_active_voucher},
},
},
}
@ -2064,20 +2110,21 @@ func TestManageVouchers(t *testing.T) {
mockAccountService := new(mocks.MockAccountService)
h := &MenuHandlers{
userdataStore: store,
userdataStore: userStore,
accountService: mockAccountService,
flagManager: fm,
logDb: logDb,
}
mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil)
// Store active voucher if needed
if tt.storedActiveVoucher != "" {
err := store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.storedActiveVoucher))
err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.storedActiveVoucher))
if err != nil {
t.Fatal(err)
}
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte("0x41c188D45rfg6ds"))
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte("0x41c188D45rfg6ds"))
if err != nil {
t.Fatal(err)
}
@ -2089,12 +2136,12 @@ func TestManageVouchers(t *testing.T) {
if tt.storedActiveVoucher != "" {
// Validate stored voucher symbols
voucherData, err := store.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS)
voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS)
assert.NoError(t, err)
assert.Equal(t, tt.expectedVoucherSymbols, voucherData)
// Validate stored active contract address
updatedAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS)
updatedAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS)
assert.NoError(t, err)
assert.Equal(t, tt.expectedUpdatedAddress, updatedAddress)
@ -2205,6 +2252,8 @@ func TestGetVoucherDetails(t *testing.T) {
if err != nil {
t.Logf(err.Error())
}
flag_api_error, _ := fm.GetFlag("flag_api_call_error")
mockAccountService := new(mocks.MockAccountService)
sessionId := "session123"
@ -2232,8 +2281,8 @@ func TestGetVoucherDetails(t *testing.T) {
"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(""))
expectedResult.FlagReset = append(expectedResult.FlagReset, flag_api_error)
assert.NoError(t, err)
assert.Equal(t, expectedResult, res)
}
@ -2336,10 +2385,8 @@ func TestCheckBlockedStatus(t *testing.T) {
if err != nil {
t.Logf(err.Error())
}
flag_account_blocked, err := fm.GetFlag("flag_account_blocked")
if err != nil {
t.Logf(err.Error())
}
flag_account_blocked, _ := fm.GetFlag("flag_account_blocked")
flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset")
h := &MenuHandlers{
userdataStore: store,
@ -2354,13 +2401,15 @@ func TestCheckBlockedStatus(t *testing.T) {
{
name: "Currently blocked account",
currentWrongPinAttempts: "4",
expectedResult: resource.Result{},
expectedResult: resource.Result{
FlagReset: []uint32{flag_account_pin_reset},
},
},
{
name: "Account with 0 wrong PIN attempts",
currentWrongPinAttempts: "0",
expectedResult: resource.Result{
FlagReset: []uint32{flag_account_blocked},
FlagReset: []uint32{flag_account_pin_reset, flag_account_blocked},
},
},
}
@ -2421,8 +2470,14 @@ func TestCheckTransactions(t *testing.T) {
sessionId := "session123"
publicKey := "0X13242618721"
ctx, store := InitializeTestStore(t)
ctx, userStore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
_, logdb := InitializeTestLogdbStore(t)
logDb := store.LogDb{
Db: logdb,
}
spdb := InitializeTestSubPrefixDb(t, ctx)
fm, err := NewFlagManager(flagsPath)
@ -2431,13 +2486,14 @@ func TestCheckTransactions(t *testing.T) {
}
h := &MenuHandlers{
userdataStore: store,
userdataStore: userStore,
accountService: mockAccountService,
prefixDb: spdb,
logDb: logDb,
flagManager: fm,
}
err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil {
t.Fatal(err)
}
@ -2889,173 +2945,6 @@ func TestValidateBlockedNumber(t *testing.T) {
}
}
func TestSaveOthersTemporaryPin(t *testing.T) {
sessionId := "session123"
blockedNumber := "+254712345678"
testPin := "1234"
ctx, userStore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
h := &MenuHandlers{
userdataStore: userStore,
}
tests := []struct {
name string
sessionId string
blockedNumber string
testPin string
setup func() error // Setup function for each test case
expectedError bool
verifyResult func(t *testing.T) // Function to verify the result
}{
{
name: "Missing Session ID",
sessionId: "", // Empty session ID
blockedNumber: blockedNumber,
testPin: testPin,
setup: nil,
expectedError: true,
verifyResult: nil,
},
{
name: "Failed to Read Blocked Number",
sessionId: sessionId,
blockedNumber: blockedNumber,
testPin: testPin,
setup: func() error {
// Do not write the blocked number to simulate a read failure
return nil
},
expectedError: true,
verifyResult: nil,
},
{
name: "Successfully save hashed PIN",
sessionId: sessionId,
blockedNumber: blockedNumber,
testPin: testPin,
setup: func() error {
// Write the blocked number to the store
return userStore.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(blockedNumber))
},
expectedError: false,
verifyResult: func(t *testing.T) {
// Read the stored hashed PIN
othersHashedPin, err := userStore.ReadEntry(ctx, blockedNumber, storedb.DATA_TEMPORARY_VALUE)
if err != nil {
t.Fatal(err)
}
// Verify that the stored hashed PIN matches the original PIN
if !pin.VerifyPIN(string(othersHashedPin), testPin) {
t.Fatal("stored hashed PIN does not match the original PIN")
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set up the context with the session ID
ctx := context.WithValue(context.Background(), "SessionId", tt.sessionId)
// Run the setup function if provided
if tt.setup != nil {
err := tt.setup()
if err != nil {
t.Fatal(err)
}
}
// Call the function under test
_, err := h.SaveOthersTemporaryPin(ctx, "save_others_temporary_pin", []byte(tt.testPin))
// Assert the error
if tt.expectedError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
// Verify the result if a verification function is provided
if tt.verifyResult != nil {
tt.verifyResult(t)
}
})
}
}
func TestCheckBlockedNumPinMisMatch(t *testing.T) {
sessionId := "session123"
blockedNumber := "+254712345678"
testPin := "1234"
mockState := state.NewState(128)
ctx, userStore := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
hashedPIN, err := pin.HashPIN(testPin)
if err != nil {
logg.ErrorCtxf(ctx, "failed to hash testPin", "error", err)
t.Fatal(err)
}
fm, err := NewFlagManager(flagsPath)
if err != nil {
t.Fatal(err)
}
flag_pin_mismatch, _ := fm.GetFlag("flag_pin_mismatch")
h := &MenuHandlers{
userdataStore: userStore,
st: mockState,
flagManager: fm,
}
// Write initial data to the store
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(blockedNumber))
if err != nil {
t.Fatal(err)
}
err = userStore.WriteEntry(ctx, blockedNumber, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
input []byte
expectedResult resource.Result
}{
{
name: "Successful PIN match",
input: []byte(testPin),
expectedResult: resource.Result{
FlagReset: []uint32{flag_pin_mismatch},
},
},
{
name: "PIN mismatch",
input: []byte("1345"),
expectedResult: resource.Result{
FlagSet: []uint32{flag_pin_mismatch},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := h.CheckBlockedNumPinMisMatch(ctx, "sym", tt.input)
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, res)
})
}
}
func TestGetCurrentProfileInfo(t *testing.T) {
sessionId := "session123"
ctx, store := InitializeTestStore(t)
@ -3212,30 +3101,6 @@ func TestResetOthersPin(t *testing.T) {
assert.NoError(t, err)
}
func TestResetValidPin(t *testing.T) {
ctx := context.Background()
fm, err := NewFlagManager(flagsPath)
if err != nil {
t.Fatal(err)
}
flag_valid_pin, _ := fm.GetFlag("flag_valid_pin")
expectedResult := resource.Result{
FlagReset: []uint32{flag_valid_pin},
}
h := &MenuHandlers{
flagManager: fm,
}
res, err := h.ResetValidPin(ctx, "reset_valid_pin", []byte(""))
assert.NoError(t, err)
assert.Equal(t, expectedResult, res)
}
func TestResetUnregisteredNumber(t *testing.T) {
ctx := context.Background()

View File

@ -27,6 +27,7 @@ type LocalHandlerService struct {
DbRs *resource.DbResource
Pe *persist.Persister
UserdataStore *db.Db
LogDb *db.Db
Cfg engine.Config
Rs resource.Resource
first resource.EntryFunc
@ -57,12 +58,16 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) {
ls.UserdataStore = db
}
func (ls *LocalHandlerService) SetLogDb(db *db.Db) {
ls.LogDb = db
}
func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) (*application.MenuHandlers, error) {
replaceSeparatorFunc := func(input string) string {
return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator)
}
appHandlers, err := application.NewMenuHandlers(ls.Parser, *ls.UserdataStore, accountService, replaceSeparatorFunc)
appHandlers, err := application.NewMenuHandlers(ls.Parser, *ls.UserdataStore, *ls.LogDb, accountService, replaceSeparatorFunc)
if err != nil {
return nil, err
}
@ -99,7 +104,6 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
ls.DbRs.AddLocalFunc("verify_yob", appHandlers.VerifyYob)
ls.DbRs.AddLocalFunc("reset_incorrect_date_format", appHandlers.ResetIncorrectYob)
ls.DbRs.AddLocalFunc("initiate_transaction", appHandlers.InitiateTransaction)
ls.DbRs.AddLocalFunc("verify_new_pin", appHandlers.VerifyNewPin)
ls.DbRs.AddLocalFunc("confirm_pin_change", appHandlers.ConfirmPinChange)
ls.DbRs.AddLocalFunc("quit_with_help", appHandlers.QuitWithHelp)
ls.DbRs.AddLocalFunc("fetch_community_balance", appHandlers.FetchCommunityBalance)
@ -108,13 +112,10 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
ls.DbRs.AddLocalFunc("view_voucher", appHandlers.ViewVoucher)
ls.DbRs.AddLocalFunc("set_voucher", appHandlers.SetVoucher)
ls.DbRs.AddLocalFunc("get_voucher_details", appHandlers.GetVoucherDetails)
ls.DbRs.AddLocalFunc("reset_valid_pin", appHandlers.ResetValidPin)
ls.DbRs.AddLocalFunc("check_pin_mismatch", appHandlers.CheckBlockedNumPinMisMatch)
ls.DbRs.AddLocalFunc("validate_blocked_number", appHandlers.ValidateBlockedNumber)
ls.DbRs.AddLocalFunc("retrieve_blocked_number", appHandlers.RetrieveBlockedNumber)
ls.DbRs.AddLocalFunc("reset_unregistered_number", appHandlers.ResetUnregisteredNumber)
ls.DbRs.AddLocalFunc("reset_others_pin", appHandlers.ResetOthersPin)
ls.DbRs.AddLocalFunc("save_others_temporary_pin", appHandlers.SaveOthersTemporaryPin)
ls.DbRs.AddLocalFunc("get_current_profile_info", appHandlers.GetCurrentProfileInfo)
ls.DbRs.AddLocalFunc("check_transactions", appHandlers.CheckTransactions)
ls.DbRs.AddLocalFunc("get_transactions", appHandlers.GetTransactionsList)
@ -128,6 +129,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
ls.DbRs.AddLocalFunc("get_suggested_alias", appHandlers.GetSuggestedAlias)
ls.DbRs.AddLocalFunc("confirm_new_alias", appHandlers.ConfirmNewAlias)
ls.DbRs.AddLocalFunc("check_account_created", appHandlers.CheckAccountCreated)
ls.DbRs.AddLocalFunc("reset_api_call_failure", appHandlers.ResetApiCallFailure)
ls.first = appHandlers.Init

96
internal/sms/sms.go Normal file
View File

@ -0,0 +1,96 @@
package sms
import (
"context"
"fmt"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/grassrootseconomics/common/phone"
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
)
var (
logg = logging.NewVanilla().WithDomain("smsservice")
)
type SmsService struct {
Accountservice remote.AccountService
Userdatastore store.UserDataStore
}
// SendUpsellSMS will send an invitation SMS to an unregistered phone number
func (smsservice *SmsService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) error {
if !phone.IsValidPhoneNumber(inviterPhone) {
return fmt.Errorf("invalid inviter phone number %v", inviterPhone)
}
if !phone.IsValidPhoneNumber(inviteePhone) {
return fmt.Errorf("Invalid invitee phone number %v", inviteePhone)
}
_, err := smsservice.Accountservice.SendUpsellSMS(ctx, inviterPhone, inviteePhone)
if err != nil {
return fmt.Errorf("Failed to send upsell sms: %v", err)
}
return nil
}
// sendPINResetSMS will send an SMS to a user's phonenumber in the event that the associated account's PIN has been reset.
func (smsService *SmsService) SendPINResetSMS(ctx context.Context, adminPhoneNumber, blockedPhoneNumber string) error {
formattedAdminPhone, err := phone.FormatPhoneNumber(adminPhoneNumber)
if err != nil {
return fmt.Errorf("failed to format admin phone number: %w", err)
}
formattedBlockedPhone, err := phone.FormatPhoneNumber(blockedPhoneNumber)
if err != nil {
return fmt.Errorf("failed to format blocked phone number: %w", err)
}
if !phone.IsValidPhoneNumber(formattedAdminPhone) {
return fmt.Errorf("invalid admin phone number")
}
if !phone.IsValidPhoneNumber(formattedBlockedPhone) {
return fmt.Errorf("invalid blocked phone number")
}
err = smsService.Accountservice.SendPINResetSMS(ctx, formattedAdminPhone, formattedBlockedPhone)
if err != nil {
return fmt.Errorf("failed to send pin reset sms: %v", err)
}
return nil
}
// SendAddressSMS will triger an SMS when a user navigates to the my address node.The SMS will be sent to the associated phonenumber.
func (smsService *SmsService) SendAddressSMS(ctx context.Context) error {
store := smsService.Userdatastore
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return fmt.Errorf("missing session")
}
publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
return err
}
originPhone, err := phone.FormatPhoneNumber(sessionId)
if err != nil {
logg.DebugCtxf(ctx, "Failed to format origin phonenumber", "sessionid", sessionId)
return nil
}
if !phone.IsValidPhoneNumber(originPhone) {
logg.InfoCtxf(ctx, "Invalid origin phone number", "origin phonenumber", originPhone)
return fmt.Errorf("invalid origin phone number")
}
err = smsService.Accountservice.SendAddressSMS(ctx, string(publicKey), originPhone)
if err != nil {
logg.DebugCtxf(ctx, "Failed to send address sms", "error", err)
return fmt.Errorf("Failed to send address sms: %v", err)
}
return nil
}

View File

@ -202,23 +202,7 @@
},
{
"input": "0700000000",
"expectedContent": "Please enter new PIN for: {secondary_session_id}\n0:Back"
},
{
"input": "11111",
"expectedContent": "The PIN you have entered is invalid.Please try a 4 digit number instead.\n1:Retry\n9:Quit"
},
{
"input": "1",
"expectedContent": "Please enter new PIN for: {secondary_session_id}\n0:Back"
},
{
"input": "1111",
"expectedContent": "Please confirm new PIN for: {secondary_session_id}\n0:Back"
},
{
"input": "1111",
"expectedContent": "Please enter your PIN:"
"expectedContent": "{secondary_session_id} will get a PIN reset request.\nPlease enter your PIN to confirm:\n0:Back\n9:Quit"
},
{
"input": "1234",
@ -612,7 +596,7 @@
},
{
"input": "1234",
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 80\nLocation: Kilifi\nYou provide: Bananas\nYour alias: \n\n0:Back\n9:Quit"
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 80\nLocation: Kilifi\nYou provide: Bananas\nYour alias: Not Provided\n\n0:Back\n9:Quit"
},
{
"input": "0",

View File

@ -1,5 +1,4 @@
LOAD check_identifier 0
RELOAD check_identifier
MAP check_identifier
MOUT back 0
MOUT quit 9

View File

@ -6,7 +6,7 @@ MOUT back 0
HALT
LOAD validate_amount 64
RELOAD validate_amount
CATCH api_failure flag_api_call_error 1
CATCH api_failure flag_api_call_error 1
CATCH invalid_amount flag_invalid_amount 1
INCMP _ 0
LOAD get_recipient 0

View File

@ -1 +1 @@
Failed to connect to the custodial service.Please try again.
Failed to connect to the custodial service .Please try again.

View File

@ -1,5 +1,7 @@
LOAD reset_api_call_failure 6
RELOAD reset_api_call_failure
MOUT retry 1
MOUT quit 9
HALT
INCMP _ 1
INCMP ^ 1
INCMP quit 9

View File

@ -0,0 +1 @@
Imeshindwa kuunganisha kwenye huduma ya uangalizi. Tafadhali jaribu tena.

View File

@ -0,0 +1,2 @@
{{.retrieve_blocked_number}} will get a PIN reset request.
Please enter your PIN to confirm:

View File

@ -0,0 +1,12 @@
LOAD retrieve_blocked_number 0
RELOAD retrieve_blocked_number
MAP retrieve_blocked_number
MOUT back 0
MOUT quit 9
LOAD authorize_account 6
HALT
RELOAD authorize_account
CATCH incorrect_pin flag_incorrect_pin 1
INCMP _ 0
INCMP quit 9
INCMP pin_reset_result *

View File

@ -0,0 +1,2 @@
{{.retrieve_blocked_number}} atapokea ombi la kuweka upya PIN.
Tafadhali weka PIN yako kudhibitisha:

View File

@ -1,5 +1,6 @@
LOAD check_transactions 0
RELOAD check_transactions
CATCH api_failure flag_api_call_error 1
CATCH no_transfers flag_no_transfers 1
LOAD authorize_account 6
MOUT back 0

View File

@ -1,6 +1,6 @@
LOAD reset_incorrect 6
LOAD fetch_community_balance 0
CATCH api_failure flag_api_call_error 1
CATCH api_failure flag_api_call_error 1
MAP fetch_community_balance
CATCH incorrect_pin flag_incorrect_pin 1
CATCH pin_entry flag_account_authorized 0

View File

@ -1,4 +1,7 @@
LOAD save_temporary_pin 6
MOUT back 0
HALT
INCMP _ 0
LOAD verify_create_pin 8
RELOAD verify_create_pin
CATCH pin_mismatch flag_pin_mismatch 1
INCMP account_creation *

View File

@ -8,5 +8,5 @@ HALT
INCMP _ 0
RELOAD authorize_account
CATCH incorrect_pin flag_incorrect_pin 1
CATCH invalid_pin flag_invalid_pin 1
CATCH invalid_pin flag_invalid_pin 1
CATCH update_alias flag_allow_update 1

View File

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

View File

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

View File

@ -1 +0,0 @@
Tafadhali thibitisha PIN mpya ya: {{.retrieve_blocked_number}}

View File

@ -1,7 +1,7 @@
LOAD confirm_pin_change 0
MOUT back 0
HALT
INCMP _ 0
LOAD confirm_pin_change 0
RELOAD confirm_pin_change
CATCH pin_reset_mismatch flag_pin_mismatch 1
INCMP * pin_reset_success
CATCH pin_mismatch flag_pin_mismatch 1
INCMP pin_reset_success *

View File

@ -2,8 +2,8 @@ LOAD create_account 0
CATCH account_creation_failed flag_account_creation_failed 1
MOUT exit 0
HALT
INCMP quit 0
LOAD save_temporary_pin 6
RELOAD save_temporary_pin
CATCH . flag_incorrect_pin 1
INCMP quit 0
CATCH invalid_pin flag_invalid_pin 1
INCMP confirm_create_pin *

View File

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

View File

@ -7,4 +7,4 @@ INCMP _ 0
LOAD validate_blocked_number 6
RELOAD validate_blocked_number
CATCH unregistered_number flag_unregistered_number 1
INCMP enter_others_new_pin *
INCMP authorize_reset_others_pin *

View File

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

View File

@ -1,9 +0,0 @@
LOAD retrieve_blocked_number 0
RELOAD retrieve_blocked_number
MAP retrieve_blocked_number
MOUT back 0
HALT
LOAD verify_new_pin 6
RELOAD verify_new_pin
INCMP _ 0
INCMP * confirm_others_new_pin

View File

@ -1 +0,0 @@
Tafadhali weka PIN mpya ya: {{.retrieve_blocked_number}}

View File

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

View File

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

View File

@ -1 +1 @@
The PIN you entered is invalid.The PIN must be a 4 digit number.
The PIN you entered is invalid. The PIN must be a 4 digit number.

View File

@ -2,6 +2,7 @@ LOAD clear_temporary_value 2
RELOAD clear_temporary_value
LOAD manage_vouchers 160
RELOAD manage_vouchers
CATCH api_failure flag_api_call_error 1
LOAD check_balance 128
RELOAD check_balance
CATCH api_failure flag_api_call_error 1

View File

@ -9,7 +9,7 @@ MOUT my_address 6
MOUT my_account_alias 7
MOUT back 0
HALT
INCMP main 0
INCMP ^ 0
INCMP edit_profile 1
INCMP change_language 2
INCMP balances 3

View File

@ -5,4 +5,5 @@ HALT
INCMP _ 0
LOAD request_custom_alias 0
RELOAD request_custom_alias
INCMP confirm_new_alias *
CATCH api_failure flag_api_call_error 1
INCMP confirm_new_alias *

View File

@ -1,6 +1,6 @@
LOAD reset_incorrect 6
LOAD check_balance 0
CATCH api_failure flag_api_call_error 1
CATCH api_failure flag_api_call_error 1
MAP check_balance
CATCH incorrect_pin flag_incorrect_pin 1
CATCH pin_entry flag_account_authorized 0

View File

@ -2,6 +2,5 @@ MOUT back 0
HALT
INCMP _ 0
RELOAD save_temporary_pin
RELOAD verify_new_pin
CATCH invalid_pin flag_valid_pin 0
INCMP * confirm_pin_change
CATCH invalid_pin flag_invalid_pin 1
INCMP confirm_pin_change *

View File

@ -4,5 +4,5 @@ HALT
INCMP _ 0
RELOAD authorize_account
CATCH incorrect_pin flag_incorrect_pin 1
CATCH invalid_pin flag_invalid_pin 1
CATCH invalid_pin flag_invalid_pin 1
INCMP new_pin *

View File

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

View File

@ -1 +0,0 @@
PIN uliyoweka hailingani.Jaribu tena.

View File

@ -1,7 +1,6 @@
LOAD set_back 6
LOAD authorize_account 16
LOAD reset_allow_update 4
LOAD verify_new_pin 2
LOAD save_temporary_pin 1
LOAD reset_incorrect 0
LOAD reset_invalid_pin 6

View File

@ -1 +0,0 @@
The PIN is not a match. Try again

View File

@ -1,6 +0,0 @@
MOUT retry 1
MOUT quit 9
HALT
INCMP _ 1
INCMP quit 9
INCMP . *

View File

@ -1 +0,0 @@
PIN uliyoweka hailingani.Jaribu tena.

View File

@ -1,3 +1,4 @@
CATCH _ flag_account_authorized 0
LOAD retrieve_blocked_number 0
MAP retrieve_blocked_number
LOAD reset_others_pin 6
@ -5,4 +6,4 @@ MOUT back 0
MOUT quit 9
HALT
INCMP ^ 0
INCMP quit 9
INCMP quit 9

View File

@ -1,6 +1,6 @@
MOUT back 0
MOUT quit 9
HALT
INCMP main 0
INCMP ^ 0
INCMP quit 9
INCMP . *

View File

@ -9,7 +9,7 @@ flag,flag_account_authorized,15,this is set to allow a user access guarded nodes
flag,flag_invalid_recipient,16,this is set when the transaction recipient is invalid
flag,flag_invalid_recipient_with_invite,17,this is set when the transaction recipient is valid but not on the platform
flag,flag_invalid_amount,18,this is set when the given transaction amount is invalid
flag,flag_incorrect_pin,19,this is set when the provided PIN is invalid or does not match the current account's PIN
flag,flag_incorrect_pin,19,this is set when the provided PIN does not match the current account's PIN
flag,flag_valid_pin,20,this is set when the given PIN is valid
flag,flag_allow_update,21,this is set to allow a user to update their profile data
flag,flag_single_edit,22,this is set to allow a user to edit a single profile item such as year of birth
@ -31,3 +31,4 @@ flag,flag_back_set,37,this is set when it is a back navigation
flag,flag_account_blocked,38,this is set when an account has been blocked after the allowed incorrect PIN attempts have been exceeded
flag,flag_invalid_pin,39,this is set when the given PIN is invalid(is less than or more than 4 digits)
flag,flag_alias_set,40,this is set when an account alias has been assigned to a user
flag,flag_account_pin_reset,41,this is set on an account when an admin triggers a PIN reset for them

1 flag flag_language_set 8 checks whether the user has set their prefered language
9 flag flag_invalid_recipient 16 this is set when the transaction recipient is invalid
10 flag flag_invalid_recipient_with_invite 17 this is set when the transaction recipient is valid but not on the platform
11 flag flag_invalid_amount 18 this is set when the given transaction amount is invalid
12 flag flag_incorrect_pin 19 this is set when the provided PIN is invalid or does not match the current account's PIN this is set when the provided PIN does not match the current account's PIN
13 flag flag_valid_pin 20 this is set when the given PIN is valid
14 flag flag_allow_update 21 this is set to allow a user to update their profile data
15 flag flag_single_edit 22 this is set to allow a user to edit a single profile item such as year of birth
31 flag flag_account_blocked 38 this is set when an account has been blocked after the allowed incorrect PIN attempts have been exceeded
32 flag flag_invalid_pin 39 this is set when the given PIN is invalid(is less than or more than 4 digits)
33 flag flag_alias_set 40 this is set when an account alias has been assigned to a user
34 flag flag_account_pin_reset 41 this is set on an account when an admin triggers a PIN reset for them

View File

@ -2,13 +2,14 @@ LOAD check_blocked_status 1
RELOAD check_blocked_status
LOAD check_account_created 2
RELOAD check_account_created
CATCH self_reset_pin flag_account_pin_reset 1
CATCH blocked_account flag_account_blocked 1
CATCH select_language flag_language_set 0
CATCH terms flag_account_created 0
CATCH create_pin flag_pin_set 0
LOAD check_account_status 0
RELOAD check_account_status
CATCH api_failure flag_api_call_error 1
CATCH api_failure flag_api_call_error 1
CATCH account_pending flag_account_pending 1
CATCH create_pin flag_pin_set 0
CATCH main flag_account_success 1
HALT

View File

@ -0,0 +1,2 @@
A PIN reset has been done on your account.
Please enter a new four number PIN:

View File

@ -0,0 +1,6 @@
LOAD reset_invalid_pin 6
HALT
LOAD save_temporary_pin 1
RELOAD save_temporary_pin
CATCH invalid_pin flag_invalid_pin 1
INCMP confirm_pin_change *

View File

@ -0,0 +1,2 @@
Uwekaji upya wa PIN umefanyika kwenye akaunti yako.
Tafadhali weka PIN mpya ya nambari nne:

View File

@ -5,6 +5,7 @@ MOUT back 0
HALT
LOAD validate_recipient 50
RELOAD validate_recipient
CATCH api_failure flag_api_call_error 1
CATCH invalid_recipient flag_invalid_recipient 1
CATCH invite_recipient flag_invalid_recipient_with_invite 1
INCMP _ 0

View File

@ -1,5 +1,7 @@
CATCH no_voucher flag_no_active_voucher 1
LOAD get_voucher_details 0
RELOAD get_voucher_details
CATCH api_failure flag_api_call_error 1
MAP get_voucher_details
MOUT back 0
HALT

View File

@ -65,6 +65,8 @@ const (
DATA_ACCOUNT_ALIAS
//currently suggested alias by the api awaiting user's confirmation as accepted account alias
DATA_SUGGESTED_ALIAS
//Key used to store a value of 1 for a user to reset their own PIN once they access the menu.
DATA_SELF_PIN_RESET
)
const (

27
store/log_db.go Normal file
View File

@ -0,0 +1,27 @@
package store
import (
"context"
visedb "git.defalsify.org/vise.git/db"
"git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
)
type LogDb struct {
visedb.Db
}
func (db *LogDb) WriteLogEntry(ctx context.Context, sessionId string, typ db.DataTyp, v []byte) error {
db.SetPrefix(visedb.DATATYPE_USERDATA)
db.SetSession(sessionId)
k := storedb.ToBytes(typ)
return db.Put(ctx, k, v)
}
func (db *LogDb) ReadLogEntry(ctx context.Context, sessionId string, typ db.DataTyp) ([]byte, error) {
db.SetPrefix(visedb.DATATYPE_USERDATA)
db.SetSession(sessionId)
k := storedb.ToBytes(typ)
return db.Get(ctx, k)
}

View File

@ -113,6 +113,12 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool, *persist.Pe
os.Exit(1)
}
logdb, err := menuStorageService.GetLogDb(ctx, userDataStore, "test-db-logs", "user-data")
if err != nil {
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
os.Exit(1)
}
dbResource, ok := rs.(*resource.DbResource)
if !ok {
fmt.Fprintf(os.Stderr, "dbresource cast error")
@ -121,6 +127,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool, *persist.Pe
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userDataStore)
lhs.SetLogDb(&logdb)
lhs.SetPersister(pe)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
@ -154,6 +161,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool, *persist.Pe
en := lhs.GetEngine(lhs.Cfg, rs, pe)
cleanFn := func() {
logdb.Close(ctx)
err := en.Finish(ctx)
if err != nil {
logg.Errorf(err.Error())