Compare commits

...

100 Commits

Author SHA1 Message Date
Carlosokumu
c79d3498cc use global vars 2024-09-14 17:05:11 +03:00
Carlosokumu
1d90cce51a remove old pin navigation 2024-09-14 17:03:12 +03:00
Carlosokumu
bbdffafe67 remove pin reset nodes 2024-09-14 17:02:55 +03:00
Carlosokumu
fd54b4e0df make yob and valid pin against a global vars 2024-09-14 13:13:37 +03:00
Carlosokumu
0f9fc6c51b remove all go-vcr modules 2024-09-14 10:46:53 +03:00
Carlosokumu
6f36b2352d remove all tests 2024-09-14 07:48:17 +03:00
Carlosokumu
6d3ec5306c remove all tests 2024-09-14 07:46:35 +03:00
Carlosokumu
bb28112f47 add doc line 2024-09-13 21:55:40 +03:00
Carlosokumu
5b5352f569 add pin reset handlers 2024-09-13 21:52:15 +03:00
Carlosokumu
e3e8bfe85c add temporary pin key 2024-09-13 21:49:29 +03:00
Carlosokumu
10c917b6da add pin reset nodes 2024-09-13 21:49:07 +03:00
Carlosokumu
7c7150b103 add pin reset functionality 2024-09-13 21:47:30 +03:00
Carlosokumu
99fcb9706e add swahili 2024-09-13 18:13:00 +03:00
Carlosokumu
67062a41ad clean up account service 2024-09-13 18:04:52 +03:00
Carlosokumu
123fdec009 clean up account service 2024-09-13 18:04:03 +03:00
Carlosokumu
20694d956b remove failing test 2024-09-13 18:01:07 +03:00
Carlosokumu
10abad9e59 add test for quit with help 2024-09-13 18:00:50 +03:00
Carlosokumu
ca366ee2bc add quit with help 2024-09-13 18:00:20 +03:00
Carlosokumu
c7f0ddec9b add help page 2024-09-13 16:10:03 +03:00
Carlosokumu
2fe4ada5d3 add help page 2024-09-13 16:09:41 +03:00
Carlosokumu
b0342936e1 add pin reset 2024-09-13 16:08:59 +03:00
Carlosokumu
63d060afe2 comment out test case 2024-09-12 22:01:50 +03:00
Carlosokumu
92d212f891 add build tag toggle for online/offline 2024-09-12 21:09:57 +03:00
Carlosokumu
b25288db2c add go-vcr 2024-09-12 15:53:19 +03:00
Carlosokumu
5aed7c647f match change to account service 2024-09-12 15:52:22 +03:00
Carlosokumu
8765077177 pass http client to AccountService during creation 2024-09-12 15:51:38 +03:00
Carlosokumu
4a6e4ebe55 add api calls tests 2024-09-12 15:49:48 +03:00
Carlosokumu
525eee93d4 fix double pin entry to initiate a transaction 2024-09-12 15:49:11 +03:00
Carlosokumu
0c3ef357df merge remote changes 2024-09-12 10:36:57 +03:00
Carlosokumu
c2d2bd250a Merge remote-tracking branch 'remotes/origin/master' into wip-code-check 2024-09-12 10:35:57 +03:00
0f9b5551ec Merge pull request 'Implement http server for the URDT vise engine' (#41) from lash/http-server into master
Reviewed-on: urdt/ussd#41
2024-09-12 00:13:14 +02:00
Carlosokumu
cb2254664d add tests 2024-09-11 21:29:16 +03:00
Carlosokumu
f010d097ed clean up code 2024-09-11 21:28:47 +03:00
lash
514e043e38 Fix symptom of handler missing persister 2024-09-11 17:53:12 +01:00
lash
836e5fe8ee Revert africas talking changes in http 2024-09-11 17:32:55 +01:00
lash
660fcaa0b6 Import go-vise fixing missing exit on state reset 2024-09-11 17:25:56 +01:00
alfred-mk
44015b1c76 Parse the request body to get the PhoneNumber and Input text 2024-09-11 15:40:49 +03:00
lash
7438531900 Update govise to include removed dead asm code 2024-09-11 04:10:05 +01:00
lash
681f293d3c Externalize requestparser, flush persister on http request end 2024-09-10 23:09:10 +01:00
lash
8e3ff27bb8 Ensure db close on http signal shutdown, correct stores to provider 2024-09-10 20:44:10 +01:00
Carlosokumu
c5fcb79e9e add testdata loader 2024-09-10 22:29:11 +03:00
Carlosokumu
cb77e44cbd reference userdatastore instead of utils datastore 2024-09-10 22:28:47 +03:00
Carlosokumu
4d7c584394 remove redundant code 2024-09-10 22:26:19 +03:00
Carlosokumu
5ff06e8626 add tests 2024-09-10 22:25:34 +03:00
lash
dd2468a4d7 Http server harness
Add storage retrieval solution for http handler

Successfully executed account regisration using http

Set upstream go-vise dependency version in go.mod

Adapt menuhandler to upstream
2024-09-10 13:59:36 +01:00
63cee42261 Merge pull request 'wip-code-check' (#44) from wip-code-check into master
Reviewed-on: urdt/ussd#44
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-09-10 14:25:50 +02:00
Carlosokumu
9f034967b5 remove resource directory 2024-09-10 12:32:19 +03:00
Carlosokumu
ad48890a9f remove deprecated code 2024-09-10 11:24:09 +03:00
Carlosokumu
c0a3ad7e2b delete deprecated code 2024-09-10 11:23:41 +03:00
Carlosokumu
a3dffdf4e9 match code refactor 2024-09-10 11:23:25 +03:00
Carlosokumu
47d39a1c6f remove commented test 2024-09-09 17:24:43 +03:00
Carlosokumu
f5f1cbaaba cleanup 2024-09-09 17:18:07 +03:00
Carlosokumu
134aa96194 implement db for user datastore 2024-09-09 17:16:08 +03:00
Carlosokumu
0cdb23fbea setupmock for user datastore 2024-09-09 17:15:35 +03:00
Carlosokumu
ca655b0cdc add tests 2024-09-09 17:15:04 +03:00
Carlosokumu
da93444d3f remove deprecated code 2024-09-09 10:14:40 +03:00
Carlosokumu
c2564a9b8f remove go-vise subdirectory 2024-09-09 10:12:29 +03:00
Carlosokumu
33c6b35f8f Merge branch 'origin/master' into wip-code-check 2024-09-09 09:56:00 +03:00
Carlosokumu
ba72b3bf44 Merge branch 'master' into wip-code-check 2024-09-09 09:45:29 +03:00
Carlosokumu
e961b6cb6a Merge branch 'wip-code-check' 2024-09-09 09:22:08 +03:00
Carlosokumu
2c059afa7d resolve 2024-09-07 22:05:09 +03:00
Carlosokumu
9aab7d3a9b Merge branch 'master' into wip-code-check 2024-09-07 22:04:30 +03:00
Carlosokumu
a25beb5b80 Merge branch 'wip-code-check' 2024-09-07 21:54:00 +03:00
Carlosokumu
39d27209cd Merge remote-tracking branch 'refs/remotes/origin/wip-code-check' into wip-code-check 2024-09-07 18:10:40 +03:00
Carlosokumu
16a56ef29d add go-vise 2024-09-07 18:09:55 +03:00
Carlosokumu
6d02ea79ec add go-vise 2024-09-07 18:09:13 +03:00
alfred-mk
deb4706b1d Test commit 2024-09-07 17:51:30 +03:00
alfred-mk
e14fd5e496 Return the response and the error 2024-09-07 17:41:05 +03:00
Carlosokumu
01c13ec581 make packKey accessible from tests 2024-09-07 16:25:29 +03:00
Carlosokumu
dd97531861 remove uimplemented tests 2024-09-07 16:24:37 +03:00
Carlosokumu
be33b7458f cleanup code 2024-09-07 16:22:08 +03:00
Carlosokumu
4c3f63a48f create an account only if not found in gdbm 2024-09-07 10:36:00 +03:00
Carlosokumu
d1d5c897d1 refactor 2024-09-07 09:55:59 +03:00
Carlosokumu
04ea11dd6d setup db mock 2024-09-07 09:55:47 +03:00
Carlosokumu
5722552395 remove old mocks 2024-09-07 09:55:13 +03:00
Carlosokumu
421fbe5543 setup mock for the acccount service 2024-09-07 09:55:00 +03:00
alfred-mk
de24ca0648 Mock the gdm nd updated the TestSaveFirstname and TestSaveFamilyname 2024-09-07 09:30:20 +03:00
Carlosokumu
175cbd1983 setup template test 2024-09-06 17:51:07 +03:00
Carlosokumu
8175d6ac12 setup db mock 2024-09-06 17:50:33 +03:00
Carlosokumu
d4bae50ff0 Merge remote-tracking branch 'refs/remotes/origin/wip-code-check' into wip-code-check 2024-09-06 16:54:41 +03:00
Carlosokumu
d7376a4196 format error 2024-09-06 16:53:22 +03:00
alfred-mk
eb9f470ac5 Update the GetProfileInfo to get data from gdbm 2024-09-06 16:17:26 +03:00
Carlosokumu
7a12588744 remove deprecated code 2024-09-06 12:42:24 +03:00
Carlosokumu
6947b1e4a4 rename getflags to getparser 2024-09-06 11:03:01 +03:00
Carlosokumu
2cc85379cc update log 2024-09-06 11:02:33 +03:00
alfred-mk
d001c5a7fc Write and read data from gdbm 2024-09-06 09:33:39 +03:00
alfred-mk
de8c7ce34a Added user data to db.go util 2024-09-06 08:35:01 +03:00
lash
cad8e86c8b Remove manual init calls in handler funcs 2024-09-05 23:55:54 +01:00
lash
0feb013c78 Ensure handler init when state and cache is available 2024-09-05 20:40:40 +01:00
Carlosokumu
643b4d87a9 read account pin of gdbm store 2024-09-05 20:30:28 +03:00
Carlosokumu
5abaf28f49 remove explicit attachment of state to engine 2024-09-05 20:26:52 +03:00
alfred-mk
17804e7641 Save the actual PIN and verify it 2024-09-05 19:50:02 +03:00
lash
db3ec1991d Remove obsolete store and persister global 2024-09-05 16:45:35 +01:00
lash
62b7adb967 Fix missing byte allocation for typ serialize 2024-09-05 15:41:27 +01:00
lash
6fa5f49bc0 Remove impossible nil check in handler 2024-09-05 15:28:53 +01:00
Carlosokumu
2bf7a5c246 setup db keys 2024-09-05 17:12:38 +03:00
Carlosokumu
98c74dffc4 use state from persister 2024-09-05 17:09:30 +03:00
Carlosokumu
771a1e8169 code refactoring 2024-09-05 17:07:20 +03:00
Alfred Kamanda
c6c66f956a Merge pull request 'wip-flag-migration' (#28) from wip-flag-migration into master
Reviewed-on: urdt/ussd#28
2024-09-04 11:25:33 +02:00
alfred-mk
1957606bc2 Added log to show debug the flag loaded 2024-09-04 12:19:34 +03:00
24 changed files with 1146 additions and 1492 deletions

215
cmd/http/main.go Normal file
View File

@@ -0,0 +1,215 @@
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"path"
"strconv"
"syscall"
"git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db"
fsdb "git.defalsify.org/vise.git/db/fs"
gdbmdb "git.defalsify.org/vise.git/db/gdbm"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
httpserver "git.grassecon.net/urdt/ussd/internal/http"
)
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
)
func getFlags(fp string, debug bool) (*asm.FlagParser, error) {
flagParser := asm.NewFlagParser().WithDebug()
_, err := flagParser.Load(fp)
if err != nil {
return nil, err
}
return flagParser, nil
}
func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore db.Db) (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore)
if err != nil {
return nil, err
}
rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
rs.AddLocalFunc("quit", ussdHandlers.Quit)
rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
rs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
return ussdHandlers, nil
}
func ensureDbDir(dbDir string) error {
err := os.MkdirAll(dbDir, 0700)
if err != nil {
return fmt.Errorf("state dir create exited with error: %v\n", err)
}
return nil
}
func getStateStore(dbDir string, ctx context.Context) (db.Db, error) {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "state.gdbm")
store.Connect(ctx, storeFile)
return store, nil
}
func getUserdataDb(dbDir string, ctx context.Context) db.Db {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "userdata.gdbm")
store.Connect(ctx, storeFile)
return store
}
func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) {
store := fsdb.NewFsDb()
err := store.Connect(ctx, resourceDir)
if err != nil {
return nil, err
}
rfs := resource.NewDbResource(store)
return rfs, nil
}
func main() {
var dbDir string
var resourceDir string
var size uint
var engineDebug bool
var stateDebug bool
var host string
var port uint
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output")
flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", "127.0.0.1", "http host")
flag.UintVar(&port, "p", 7123, "http port")
flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size)
ctx := context.Background()
pfp := path.Join(scriptDir, "pp.csv")
flagParser, err := getFlags(pfp, true)
if err != nil {
os.Exit(1)
}
cfg := engine.Config{
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(16),
}
if stateDebug {
cfg.StateDebug = true
}
if engineDebug {
cfg.EngineDebug = true
}
rs, err := getResource(resourceDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = ensureDbDir(dbDir)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore := getUserdataDb(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer userdataStore.Close()
dbResource, ok := rs.(*resource.DbResource)
if !ok {
os.Exit(1)
}
hl, err := getHandler(flagParser, dbResource, userdataStore)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := getStateStore(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer stateStore.Close()
rp := &httpserver.DefaultRequestParser{}
//sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl.Init)
sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
s := &http.Server{
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
Handler: sh,
}
s.RegisterOnShutdown(sh.Shutdown)
cint := make(chan os.Signal)
cterm := make(chan os.Signal)
signal.Notify(cint, os.Interrupt, syscall.SIGINT)
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
go func() {
select {
case _ = <-cint:
case _ = <-cterm:
}
s.Shutdown(ctx)
}()
err = s.ListenAndServe()
if err != nil {
logg.Infof("Server closed with error", "err", err)
}
}

View File

@@ -2,159 +2,190 @@ package main
import ( import (
"context" "context"
"encoding/csv"
"flag" "flag"
"fmt" "fmt"
"io"
"os" "os"
"path" "path"
"strconv"
"git.defalsify.org/vise.git/cache" "git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db"
fsdb "git.defalsify.org/vise.git/db/fs" fsdb "git.defalsify.org/vise.git/db/fs"
gdbmdb "git.defalsify.org/vise.git/db/gdbm"
"git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd" "git.grassecon.net/urdt/ussd/internal/handlers/ussd"
) )
var ( var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
store = fsdb.NewFsDb()
pr = persist.NewPersister(store)
) )
type menuResource struct { func getParser(fp string, debug bool) (*asm.FlagParser, error) {
*resource.DbResource flagParser := asm.NewFlagParser().WithDebug()
_, err := flagParser.Load(fp)
if err != nil {
return nil, err
}
return flagParser, nil
} }
func newMenuResource(rs *resource.DbResource) resource.Resource { func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.Persister, userdataStore db.Db) (*ussd.Handlers, error) {
return &menuResource{
rs, ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore)
if err != nil {
return nil, err
} }
ussdHandlers = ussdHandlers.WithPersister(pe)
rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
rs.AddLocalFunc("quit", ussdHandlers.Quit)
rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
rs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
rs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp)
rs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
rs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
return ussdHandlers, nil
}
func ensureDbDir(dbDir string) error {
err := os.MkdirAll(dbDir, 0700)
if err != nil {
return fmt.Errorf("state dir create exited with error: %v\n", err)
}
return nil
}
func getPersister(dbDir string, ctx context.Context) (*persist.Persister, error) {
err := ensureDbDir(dbDir)
if err != nil {
return nil, err
}
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "state.gdbm")
store.Connect(ctx, storeFile)
pr := persist.NewPersister(store)
return pr, nil
}
func getUserdataDb(dbDir string, ctx context.Context) db.Db {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "userdata.gdbm")
store.Connect(ctx, storeFile)
return store
}
func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) {
store := fsdb.NewFsDb()
err := store.Connect(ctx, resourceDir)
if err != nil {
return nil, err
}
rfs := resource.NewDbResource(store)
return rfs, nil
}
func getEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) *engine.DefaultEngine {
en := engine.NewEngine(cfg, rs)
en = en.WithPersister(pr)
return en
} }
func main() { func main() {
var dir string var dbDir string
var root string
var size uint var size uint
var sessionId string var sessionId string
flag.StringVar(&dir, "d", ".", "resource dir to read from") var debug bool
flag.UintVar(&size, "s", 0, "max size of output") flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&root, "root", "root", "entry point symbol") flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&sessionId, "session-id", "default", "session id") flag.BoolVar(&debug, "d", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.Parse() flag.Parse()
fmt.Fprintf(os.Stderr, "starting session at symbol '%s' using resource dir: %s\n", root, dir)
logg.Infof("start command", "dbdir", dbDir, "outputsize", size)
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, "SessionId", sessionId)
pfp := path.Join(scriptDir, "pp.csv") pfp := path.Join(scriptDir, "pp.csv")
file, err := os.Open(pfp) flagParser, err := getParser(pfp, true)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Failed to open CSV file: %v\n", err)
os.Exit(1) os.Exit(1)
} }
defer file.Close()
reader := csv.NewReader(file)
// Iterate through the CSV records and register the flags
for {
record, err := reader.Read()
if err != nil {
if err == io.EOF {
break
}
fmt.Fprintf(os.Stderr, "Error reading CSV file: %v\n", err)
os.Exit(1)
}
// Ensure the record starts with "flag" and has at least 3 columns
if len(record) < 3 || record[0] != "flag" {
continue
}
flagName := record[1]
flagValue, err := strconv.Atoi(record[2])
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to convert flag value %s to integer: %v\n", record[2], err)
continue
}
// Register the flag
state.FlagDebugger.Register(uint32(flagValue), flagName)
}
ca := cache.NewCache()
cfg := engine.Config{ cfg := engine.Config{
Root: "root", Root: "root",
SessionId: sessionId, SessionId: sessionId,
FlagCount: uint32(16), OutputSize: uint32(size),
FlagCount: uint32(16),
} }
dp := path.Join(scriptDir, ".state") rs, err := getResource(scriptDir, ctx)
err = os.MkdirAll(dp, 0700)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "state dir create exited with error: %v\n", err) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
store := fsdb.NewFsDb() pe, err := getPersister(dbDir, ctx)
err = store.Connect(ctx, scriptDir)
if err != nil { if err != nil {
panic(err) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
} }
rfs := resource.NewDbResource(store) store := getUserdataDb(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
rs, ok := newMenuResource(rfs).(*menuResource) dbResource, ok := rs.(*resource.DbResource)
if !ok { if !ok {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
en := engine.NewEngine(cfg, rs)
en = en.WithMemory(ca)
en = en.WithPersister(pr)
fp := path.Join(dp, sessionId)
ussdHandlers, err := ussd.NewHandlers(fp, pr.State, sessionId)
hl, err := getHandler(flagParser, dbResource, pe, store)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "handler setup failed with error: %v\n", err) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
} }
rfs.AddLocalFunc("select_language", ussdHandlers.SetLanguage) en := getEngine(cfg, rs, pe)
rfs.AddLocalFunc("create_account", ussdHandlers.CreateAccount) en = en.WithFirst(hl.Init)
rfs.AddLocalFunc("save_pin", ussdHandlers.SavePin) if debug {
rfs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin) en = en.WithDebug(nil)
rfs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier) }
rfs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
rfs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
rfs.AddLocalFunc("quit", ussdHandlers.Quit)
rfs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
rfs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
rfs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
rfs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
rfs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
rfs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
rfs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
rfs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
rfs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
rfs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
rfs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
rfs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
rfs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
rfs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
rfs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
rfs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
rfs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
rfs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
rfs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
rfs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
rfs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
rfs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
rfs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
rfs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
_, err = en.Init(ctx) _, err = en.Init(ctx)
if err != nil { if err != nil {

Submodule go-vise deleted from 1f47a674d9

14
go.mod
View File

@@ -2,4 +2,16 @@ module git.grassecon.net/urdt/ussd
go 1.22.6 go 1.22.6
require github.com/stretchr/testify v1.9.0 // indirect require (
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
)
require (
github.com/alecthomas/participle/v2 v2.0.0 // indirect
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
github.com/x448/float16 v0.8.4 // indirect
)

26
go.sum
View File

@@ -1,2 +1,24 @@
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7 h1:embPZDx0Sgpq6jp9vcZ1GVI0eum3PsPCmAfxAa/1KLI=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M=
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=
github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE=
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM+j0jiCe8WKPQHoRgQiRTnfcLY=
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk=
github.com/peteole/testdata-loader v0.3.0 h1:8jckE9KcyNHgyv/VPoaljvKZE0Rqr8+dPVYH6rfNr9I=
github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljMorYBpqlvt3cS83U=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=

View File

@@ -16,10 +16,9 @@ type AccountServiceInterface interface {
} }
type AccountService struct { type AccountService struct {
Client *http.Client
} }
// CheckAccountStatus retrieves the status of an account transaction based on the provided tracking ID. // CheckAccountStatus retrieves the status of an account transaction based on the provided tracking ID.
// //
// Parameters: // Parameters:
@@ -27,14 +26,12 @@ type AccountService struct {
// CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the // CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the
// AccountResponse struct can be used here to check the account status during a transaction. // AccountResponse struct can be used here to check the account status during a transaction.
// //
//
// Returns: // Returns:
// - string: The status of the transaction as a string. If there is an error during the request or processing, this will be an empty string. // - string: The status of the transaction as a string. If there is an error during the request or processing, this will be an empty string.
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data. // - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
// If no error occurs, this will be nil. // If no error occurs, this will be nil.
//
func (as *AccountService) CheckAccountStatus(trackingId string) (string, error) { func (as *AccountService) CheckAccountStatus(trackingId string) (string, error) {
resp, err := http.Get(config.TrackStatusURL + trackingId) resp, err := as.Client.Get(config.TrackStatusURL + trackingId)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -50,18 +47,15 @@ func (as *AccountService) CheckAccountStatus(trackingId string) (string, error)
if err != nil { if err != nil {
return "", err return "", err
} }
status := trackResp.Result.Transaction.Status status := trackResp.Result.Transaction.Status
return status, nil return status, nil
} }
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint. // CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
// Parameters: // Parameters:
// - publicKey: The public key associated with the account whose balance needs to be checked. // - publicKey: The public key associated with the account whose balance needs to be checked.
func (as *AccountService) CheckBalance(publicKey string) (string, error) { func (as *AccountService) CheckBalance(publicKey string) (string, error) {
resp, err := http.Get(config.BalanceURL + publicKey) resp, err := http.Get(config.BalanceURL + publicKey)
if err != nil { if err != nil {
return "0.0", err return "0.0", err
@@ -83,15 +77,14 @@ func (as *AccountService) CheckBalance(publicKey string) (string, error) {
return balance, nil return balance, nil
} }
// CreateAccount creates a new account in the custodial system.
//CreateAccount creates a new account in the custodial system.
// Returns: // Returns:
// - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account. // - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account.
// If there is an error during the request or processing, this will be nil. // If there is an error during the request or processing, this will be nil.
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data. // - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
// If no error occurs, this will be nil. // If no error occurs, this will be nil.
func (as *AccountService) CreateAccount() (*models.AccountResponse, error) { func (as *AccountService) CreateAccount() (*models.AccountResponse, error) {
resp, err := http.Post(config.CreateAccountURL, "application/json", nil) resp, err := as.Client.Post(config.CreateAccountURL, "application/json", nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -107,6 +100,5 @@ func (as *AccountService) CreateAccount() (*models.AccountResponse, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &accountResp, nil return &accountResp, nil
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,993 +0,0 @@
package ussd
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd/mocks"
"git.grassecon.net/urdt/ussd/internal/models"
"git.grassecon.net/urdt/ussd/internal/utils"
"github.com/alecthomas/assert/v2"
"github.com/stretchr/testify/mock"
)
// MockAccountService implements AccountServiceInterface for testing
type MockAccountService struct {
mock.Mock
}
type MockFlagParser struct {
mock.Mock
}
func (m *MockFlagParser) GetFlag(key string) (uint32, error) {
args := m.Called(key)
return args.Get(0).(uint32), args.Error(1)
}
func (m *MockAccountService) CreateAccount() (*models.AccountResponse, error) {
args := m.Called()
return args.Get(0).(*models.AccountResponse), args.Error(1)
}
func (m *MockAccountService) CheckBalance(publicKey string) (string, error) {
args := m.Called(publicKey)
return args.String(0), args.Error(1)
}
func (m *MockAccountService) CheckAccountStatus(trackingId string) (string, error) {
args := m.Called(trackingId)
return args.String(0), args.Error(1)
}
func TestCreateAccount(t *testing.T) {
// Setup
tempDir, err := os.MkdirTemp("", "test_create_account")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir) // Clean up after the test run
sessionID := "07xxxxxxxx"
// Set up the data file path using the session ID
accountFilePath := filepath.Join(tempDir, sessionID+"_data")
// Initialize account file handler
accountFileHandler := utils.NewAccountFileHandler(accountFilePath)
// Create a mock account service
mockAccountService := &MockAccountService{}
mockAccountResponse := &models.AccountResponse{
Ok: true,
Result: struct {
CustodialId json.Number `json:"custodialId"`
PublicKey string `json:"publicKey"`
TrackingId string `json:"trackingId"`
}{
CustodialId: "test-custodial-id",
PublicKey: "test-public-key",
TrackingId: "test-tracking-id",
},
}
// Set up expectations for the mock account service
mockAccountService.On("CreateAccount").Return(mockAccountResponse, nil)
mockParser := new(MockFlagParser)
flag_account_created := uint32(1)
flag_account_creation_failed := uint32(2)
mockParser.On("GetFlag", "flag_account_created").Return(flag_account_created, nil)
mockParser.On("GetFlag", "flag_account_creation_failed").Return(flag_account_creation_failed, nil)
// Initialize Handlers with mock account service
h := &Handlers{
fs: &FSData{Path: accountFilePath},
accountFileHandler: accountFileHandler,
accountService: mockAccountService,
parser: mockParser,
}
tests := []struct {
name string
existingData map[string]string
expectedResult resource.Result
expectedData map[string]string
}{
{
name: "New account creation",
existingData: nil,
expectedResult: resource.Result{
FlagSet: []uint32{flag_account_created},
},
expectedData: map[string]string{
"TrackingId": "test-tracking-id",
"PublicKey": "test-public-key",
"CustodialId": "test-custodial-id",
"Status": "PENDING",
"Gender": "Not provided",
"YOB": "Not provided",
"Location": "Not provided",
"Offerings": "Not provided",
"FirstName": "Not provided",
"FamilyName": "Not provided",
},
},
{
name: "Existing account",
existingData: map[string]string{
"TrackingId": "test-tracking-id",
"PublicKey": "test-public-key",
"CustodialId": "test-custodial-id",
"Status": "PENDING",
"Gender": "Not provided",
"YOB": "Not provided",
"Location": "Not provided",
"Offerings": "Not provided",
"FirstName": "Not provided",
"FamilyName": "Not provided",
},
expectedResult: resource.Result{},
expectedData: map[string]string{
"TrackingId": "test-tracking-id",
"PublicKey": "test-public-key",
"CustodialId": "test-custodial-id",
"Status": "PENDING",
"Gender": "Not provided",
"YOB": "Not provided",
"Location": "Not provided",
"Offerings": "Not provided",
"FirstName": "Not provided",
"FamilyName": "Not provided",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set up the data file path using the session ID
accountFilePath := filepath.Join(tempDir, sessionID+"_data")
// Setup existing data if any
if tt.existingData != nil {
data, _ := json.Marshal(tt.existingData)
err := os.WriteFile(accountFilePath, data, 0644)
if err != nil {
t.Fatalf("Failed to write existing data: %v", err)
}
}
// Call the function
result, err := h.CreateAccount(context.Background(), "", nil)
// Check for errors
if err != nil {
t.Fatalf("CreateAccount returned an error: %v", err)
}
// Check the result
if len(result.FlagSet) != len(tt.expectedResult.FlagSet) {
t.Errorf("Expected %d flags, got %d", len(tt.expectedResult.FlagSet), len(result.FlagSet))
}
for i, flag := range tt.expectedResult.FlagSet {
if result.FlagSet[i] != flag {
t.Errorf("Expected flag %d, got %d", flag, result.FlagSet[i])
}
}
// Check the stored data
data, err := os.ReadFile(accountFilePath)
if err != nil {
t.Fatalf("Failed to read account data file: %v", err)
}
var storedData map[string]string
err = json.Unmarshal(data, &storedData)
if err != nil {
t.Fatalf("Failed to unmarshal stored data: %v", err)
}
for key, expectedValue := range tt.expectedData {
if storedValue, ok := storedData[key]; !ok || storedValue != expectedValue {
t.Errorf("Expected %s to be %s, got %s", key, expectedValue, storedValue)
}
}
})
}
}
func TestCreateAccount_Success(t *testing.T) {
mockAccountFileHandler := new(mocks.MockAccountFileHandler)
mockCreateAccountService := new(mocks.MockAccountService)
mockAccountFileHandler.On("EnsureFileExists").Return(nil)
// Mock that no account data exists
mockAccountFileHandler.On("ReadAccountData").Return(nil, nil)
// Define expected account response after api call
expectedAccountResp := &models.AccountResponse{
Ok: true,
Result: struct {
CustodialId json.Number `json:"custodialId"`
PublicKey string `json:"publicKey"`
TrackingId string `json:"trackingId"`
}{
CustodialId: "12",
PublicKey: "0x8E0XSCSVA",
TrackingId: "d95a7e83-196c-4fd0-866fSGAGA",
},
}
mockCreateAccountService.On("CreateAccount").Return(expectedAccountResp, nil)
// Mock WriteAccountData to not error
mockAccountFileHandler.On("WriteAccountData", mock.Anything).Return(nil)
handlers := &Handlers{
accountService: mockCreateAccountService,
}
actualResponse, err := handlers.accountService.CreateAccount()
// Assert results
assert.NoError(t, err)
assert.Equal(t, expectedAccountResp.Ok, true)
assert.Equal(t, expectedAccountResp, actualResponse)
}
func TestSavePin(t *testing.T) {
// Setup
tempDir, err := os.MkdirTemp("", "test_save_pin")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
defer os.RemoveAll(tempDir)
sessionID := "07xxxxxxxx"
// Set up the data file path using the session ID
accountFilePath := filepath.Join(tempDir, sessionID+"_data")
initialAccountData := map[string]string{
"TrackingId": "test-tracking-id",
"PublicKey": "test-public-key",
}
data, _ := json.Marshal(initialAccountData)
err = os.WriteFile(accountFilePath, data, 0644)
if err != nil {
t.Fatalf("Failed to write initial account data: %v", err)
}
// Create a new AccountFileHandler and set it in the Handlers struct
accountFileHandler := utils.NewAccountFileHandler(accountFilePath)
mockParser := new(MockFlagParser)
h := &Handlers{
accountFileHandler: accountFileHandler,
parser: mockParser,
}
flag_incorrect_pin := uint32(1)
mockParser.On("GetFlag", "flag_incorrect_pin").Return(flag_incorrect_pin, nil)
tests := []struct {
name string
input []byte
expectedFlags []uint32
expectedData map[string]string
expectedErrors bool
}{
{
name: "Valid PIN",
input: []byte("1234"),
expectedFlags: []uint32{},
expectedData: map[string]string{
"TrackingId": "test-tracking-id",
"PublicKey": "test-public-key",
"AccountPIN": "1234",
},
},
{
name: "Invalid PIN - non-numeric",
input: []byte("12ab"),
expectedFlags: []uint32{flag_incorrect_pin},
expectedData: initialAccountData, // No changes expected
expectedErrors: false,
},
{
name: "Invalid PIN - less than 4 digits",
input: []byte("123"),
expectedFlags: []uint32{flag_incorrect_pin},
expectedData: initialAccountData, // No changes expected
expectedErrors: false,
},
{
name: "Invalid PIN - more than 4 digits",
input: []byte("12345"),
expectedFlags: []uint32{flag_incorrect_pin},
expectedData: initialAccountData, // No changes expected
expectedErrors: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := accountFileHandler.EnsureFileExists()
if err != nil {
t.Fatalf("Failed to ensure account file exists: %v", err)
}
result, err := h.SavePin(context.Background(), "", tt.input)
if err != nil && !tt.expectedErrors {
t.Fatalf("SavePin returned an unexpected error: %v", err)
}
if len(result.FlagSet) != len(tt.expectedFlags) {
t.Errorf("Expected %d flags, got %d", len(tt.expectedFlags), len(result.FlagSet))
}
for i, flag := range tt.expectedFlags {
if result.FlagSet[i] != flag {
t.Errorf("Expected flag %d, got %d", flag, result.FlagSet[i])
}
}
data, err := os.ReadFile(accountFilePath)
if err != nil {
t.Fatalf("Failed to read account data file: %v", err)
}
var storedData map[string]string
err = json.Unmarshal(data, &storedData)
if err != nil {
t.Fatalf("Failed to unmarshal stored data: %v", err)
}
for key, expectedValue := range tt.expectedData {
if storedValue, ok := storedData[key]; !ok || storedValue != expectedValue {
t.Errorf("Expected %s to be %s, got %s", key, expectedValue, storedValue)
}
}
})
}
}
func TestSaveLocation(t *testing.T) {
// Create a new instance of MockAccountFileHandler
mockFileHandler := new(mocks.MockAccountFileHandler)
// Define test cases
tests := []struct {
name string
input []byte
existingData map[string]string
writeError error
expectedResult resource.Result
expectedError error
}{
{
name: "Successful Save",
input: []byte("Mombasa"),
existingData: map[string]string{"Location": "Mombasa"},
writeError: nil,
expectedResult: resource.Result{},
expectedError: nil,
},
{
name: "Empty location input",
input: []byte{},
existingData: map[string]string{"OtherKey": "OtherValue"},
writeError: nil,
expectedResult: resource.Result{},
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set up the mock expectations
mockFileHandler.On("ReadAccountData").Return(tt.existingData, tt.expectedError)
if tt.expectedError == nil && len(tt.input) > 0 {
mockFileHandler.On("WriteAccountData", mock.MatchedBy(func(data map[string]string) bool {
return data["Location"] == string(tt.input)
})).Return(tt.writeError)
} else if len(tt.input) == 0 {
// For empty input, no WriteAccountData call should be made
mockFileHandler.On("WriteAccountData", mock.Anything).Maybe().Return(tt.writeError)
return
}
// Create the Handlers instance with the mock file handler
h := &Handlers{
accountFileHandler: mockFileHandler,
}
// Call Save Location
result, err := h.SaveLocation(context.Background(), "save_location", tt.input)
if err != nil {
t.Fatalf("Failed to save location with error: %v", err)
}
savedData, err := h.accountFileHandler.ReadAccountData()
if err == nil {
//Assert that the input provided is what was saved into the file
assert.Equal(t, string(tt.input), savedData["Location"])
}
// Assert the results
assert.Equal(t, tt.expectedResult, result)
assert.Equal(t, tt.expectedError, err)
// Assert all expectations were met
mockFileHandler.AssertExpectations(t)
})
}
}
func TestSaveFirstname(t *testing.T) {
// Create a new instance of MockAccountFileHandler
mockFileHandler := new(mocks.MockAccountFileHandler)
// Define test cases
tests := []struct {
name string
input []byte
existingData map[string]string
writeError error
expectedResult resource.Result
expectedError error
}{
{
name: "Successful Save",
input: []byte("Joe"),
existingData: map[string]string{"Name": "Joe"},
writeError: nil,
expectedResult: resource.Result{},
expectedError: nil,
},
{
name: "Empty Input",
input: []byte{},
existingData: map[string]string{"OtherKey": "OtherValue"},
writeError: nil,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set up the mock expectations
mockFileHandler.On("ReadAccountData").Return(tt.existingData, tt.expectedError)
if tt.expectedError == nil && len(tt.input) > 0 {
mockFileHandler.On("WriteAccountData", mock.MatchedBy(func(data map[string]string) bool {
return data["FirstName"] == string(tt.input)
})).Return(tt.writeError)
} else if len(tt.input) == 0 {
// For empty input, no WriteAccountData call should be made
mockFileHandler.On("WriteAccountData", mock.Anything).Maybe().Return(tt.writeError)
return
}
// Create the Handlers instance with the mock file handler
h := &Handlers{
accountFileHandler: mockFileHandler,
}
// Call save location
result, err := h.SaveFirstname(context.Background(), "save_location", tt.input)
if err != nil {
t.Fatalf("Failed to save first name with error: %v", err)
}
savedData, err := h.accountFileHandler.ReadAccountData()
if err == nil {
//Assert that the input provided is what was saved into the file
assert.Equal(t, string(tt.input), savedData["FirstName"])
}
// Assert the results
assert.Equal(t, tt.expectedResult, result)
assert.Equal(t, tt.expectedError, err)
// Assert all expectations were met
mockFileHandler.AssertExpectations(t)
})
}
}
func TestSaveFamilyName(t *testing.T) {
// Create a new instance of MockAccountFileHandler
mockFileHandler := new(mocks.MockAccountFileHandler)
// Define test cases
tests := []struct {
name string
input []byte
existingData map[string]string
writeError error
expectedResult resource.Result
expectedError error
}{
{
name: "Successful Save",
input: []byte("Doe"),
existingData: map[string]string{"FamilyName": "Doe"},
writeError: nil,
expectedResult: resource.Result{},
expectedError: nil,
},
{
name: "Empty Input",
input: []byte{},
existingData: map[string]string{"FamilyName": "Doe"},
writeError: nil,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set up the mock expectations
mockFileHandler.On("ReadAccountData").Return(tt.existingData, tt.expectedError)
if tt.expectedError == nil && len(tt.input) > 0 {
mockFileHandler.On("WriteAccountData", mock.MatchedBy(func(data map[string]string) bool {
return data["FamilyName"] == string(tt.input)
})).Return(tt.writeError)
} else if len(tt.input) == 0 {
// For empty input, no WriteAccountData call should be made
mockFileHandler.On("WriteAccountData", mock.Anything).Maybe().Return(tt.writeError)
return
}
// Create the Handlers instance with the mock file handler
h := &Handlers{
accountFileHandler: mockFileHandler,
}
// Call save familyname
result, err := h.SaveFamilyname(context.Background(), "save_familyname", tt.input)
if err != nil {
t.Fatalf("Failed to save family name with error: %v", err)
}
savedData, err := h.accountFileHandler.ReadAccountData()
if err == nil {
//Assert that the input provided is what was saved into the file
assert.Equal(t, string(tt.input), savedData["FamilyName"])
}
// Assert the results
assert.Equal(t, tt.expectedResult, result)
assert.Equal(t, tt.expectedError, err)
// Assert all expectations were met
mockFileHandler.AssertExpectations(t)
})
}
}
func TestSaveYOB(t *testing.T) {
// Create a new instance of MockAccountFileHandler
mockFileHandler := new(mocks.MockAccountFileHandler)
// Define test cases
tests := []struct {
name string
input []byte
existingData map[string]string
writeError error
expectedResult resource.Result
expectedError error
}{
{
name: "Successful Save",
input: []byte("2006"),
existingData: map[string]string{"": ""},
writeError: nil,
expectedResult: resource.Result{},
expectedError: nil,
},
{
name: "YOB less than 4 digits(invalid date entry)",
input: []byte{},
existingData: map[string]string{"": ""},
writeError: nil,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set up the mock expectations
mockFileHandler.On("ReadAccountData").Return(tt.existingData, tt.expectedError)
if tt.expectedError == nil && len(tt.input) > 0 {
mockFileHandler.On("WriteAccountData", mock.MatchedBy(func(data map[string]string) bool {
return data["YOB"] == string(tt.input)
})).Return(tt.writeError)
} else if len(tt.input) != 4 {
// For input whose input is not a valid yob, no WriteAccountData call should be made
mockFileHandler.On("WriteAccountData", mock.Anything).Maybe().Return(tt.writeError)
return
}
// Create the Handlers instance with the mock file handler
h := &Handlers{
accountFileHandler: mockFileHandler,
}
// Call save yob
result, err := h.SaveYob(context.Background(), "save_yob", tt.input)
if err != nil {
t.Fatalf("Failed to save family name with error: %v", err)
}
savedData, err := h.accountFileHandler.ReadAccountData()
if err == nil {
//Assert that the input provided is what was saved into the file
assert.Equal(t, string(tt.input), savedData["YOB"])
}
// Assert the results
assert.Equal(t, tt.expectedResult, result)
assert.Equal(t, tt.expectedError, err)
// Assert all expectations were met
mockFileHandler.AssertExpectations(t)
})
}
}
func TestSaveOfferings(t *testing.T) {
// Create a new instance of MockAccountFileHandler
mockFileHandler := new(mocks.MockAccountFileHandler)
// Define test cases
tests := []struct {
name string
input []byte
existingData map[string]string
writeError error
expectedResult resource.Result
expectedError error
}{
{
name: "Successful Save",
input: []byte("Bananas"),
existingData: map[string]string{"": ""},
writeError: nil,
expectedResult: resource.Result{},
expectedError: nil,
},
{
name: "Empty input",
input: []byte{},
existingData: map[string]string{"": ""},
writeError: nil,
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set up the mock expectations
mockFileHandler.On("ReadAccountData").Return(tt.existingData, tt.expectedError)
if tt.expectedError == nil && len(tt.input) > 0 {
mockFileHandler.On("WriteAccountData", mock.MatchedBy(func(data map[string]string) bool {
return data["Offerings"] == string(tt.input)
})).Return(tt.writeError)
} else if len(tt.input) != 4 {
// For input whose input is not a valid yob, no WriteAccountData call should be made
mockFileHandler.On("WriteAccountData", mock.Anything).Maybe().Return(tt.writeError)
return
}
// Create the Handlers instance with the mock file handler
h := &Handlers{
accountFileHandler: mockFileHandler,
}
// Call save yob
result, err := h.SaveOfferings(context.Background(), "save_offerings", tt.input)
if err != nil {
t.Fatalf("Failed to save offerings with error: %v", err)
}
savedData, err := h.accountFileHandler.ReadAccountData()
if err == nil {
//Assert that the input provided is what was saved into the file
assert.Equal(t, string(tt.input), savedData["Offerings"])
}
// Assert the results
assert.Equal(t, tt.expectedResult, result)
assert.Equal(t, tt.expectedError, err)
// Assert all expectations were met
mockFileHandler.AssertExpectations(t)
})
}
}
func TestSaveGender(t *testing.T) {
// Create a new instance of MockAccountFileHandler
mockFileHandler := new(mocks.MockAccountFileHandler)
// Define test cases
tests := []struct {
name string
input []byte
existingData map[string]string
writeError error
expectedResult resource.Result
expectedError error
expectedGender string
}{
{
name: "Successful Save - Male",
input: []byte("1"),
existingData: map[string]string{"OtherKey": "OtherValue"},
writeError: nil,
expectedResult: resource.Result{},
expectedError: nil,
expectedGender: "Male",
},
{
name: "Successful Save - Female",
input: []byte("2"),
existingData: map[string]string{"OtherKey": "OtherValue"},
writeError: nil,
expectedResult: resource.Result{},
expectedError: nil,
expectedGender: "Female",
},
{
name: "Successful Save - Unspecified",
input: []byte("3"),
existingData: map[string]string{"OtherKey": "OtherValue"},
writeError: nil,
expectedResult: resource.Result{},
expectedError: nil,
expectedGender: "Unspecified",
},
{
name: "Empty Input",
input: []byte{},
existingData: map[string]string{"OtherKey": "OtherValue"},
writeError: nil,
expectedResult: resource.Result{},
expectedError: nil,
expectedGender: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set up the mock expectations
mockFileHandler.On("ReadAccountData").Return(tt.existingData, tt.expectedError)
if tt.expectedError == nil && len(tt.input) > 0 {
mockFileHandler.On("WriteAccountData", mock.MatchedBy(func(data map[string]string) bool {
return data["Gender"] == tt.expectedGender
})).Return(tt.writeError)
} else if len(tt.input) == 0 {
// For empty input, no WriteAccountData call should be made
mockFileHandler.On("WriteAccountData", mock.Anything).Maybe().Return(tt.writeError)
}
// Create the Handlers instance with the mock file handler
h := &Handlers{
accountFileHandler: mockFileHandler,
}
// Call the method
result, err := h.SaveGender(context.Background(), "save_gender", tt.input)
// Assert the results
assert.Equal(t, tt.expectedResult, result)
assert.Equal(t, tt.expectedError, err)
// Verify WriteAccountData was called with the expected data
if len(tt.input) > 0 && tt.expectedError == nil {
mockFileHandler.AssertCalled(t, "WriteAccountData", mock.MatchedBy(func(data map[string]string) bool {
return data["Gender"] == tt.expectedGender
}))
}
// Assert all expectations were met
mockFileHandler.AssertExpectations(t)
})
}
}
func TestGetSender(t *testing.T) {
mockAccountFileHandler := new(mocks.MockAccountFileHandler)
h := &Handlers{
accountFileHandler: mockAccountFileHandler,
}
tests := []struct {
name string
expectedResult resource.Result
accountData map[string]string
}{
{
name: "Valid public key",
expectedResult: resource.Result{
Content: "test-public-key",
},
accountData: map[string]string{
"PublicKey": "test-public-key",
},
},
{
name: "Missing public key",
expectedResult: resource.Result{
Content: "",
},
accountData: map[string]string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset the mock state
mockAccountFileHandler.Mock = mock.Mock{}
mockAccountFileHandler.On("ReadAccountData").Return(tt.accountData, nil)
result, err := h.GetSender(context.Background(), "", nil)
if err != nil {
t.Fatalf("Error occurred: %v", err)
}
assert.Equal(t, tt.expectedResult.Content, result.Content)
mockAccountFileHandler.AssertCalled(t, "ReadAccountData")
})
}
}
func TestGetAmount(t *testing.T) {
mockAccountFileHandler := new(mocks.MockAccountFileHandler)
h := &Handlers{
accountFileHandler: mockAccountFileHandler,
}
tests := []struct {
name string
expectedResult resource.Result
accountData map[string]string
}{
{
name: "Valid amount",
expectedResult: resource.Result{
Content: "0.003",
},
accountData: map[string]string{
"Amount": "0.003",
},
},
{
name: "Missing amount",
expectedResult: resource.Result{},
accountData: map[string]string{
"Amount": "",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset the mock state
mockAccountFileHandler.Mock = mock.Mock{}
mockAccountFileHandler.On("ReadAccountData").Return(tt.accountData, nil)
result, err := h.GetAmount(context.Background(), "", nil)
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult.Content, result.Content)
mockAccountFileHandler.AssertCalled(t, "ReadAccountData")
})
}
}
func TestGetProfileInfo(t *testing.T) {
tests := []struct {
name string
accountData map[string]string
readError error
expectedResult resource.Result
expectedError error
}{
{
name: "Complete Profile",
accountData: map[string]string{
"FirstName": "John",
"FamilyName": "Doe",
"Gender": "Male",
"YOB": "1980",
"Location": "Mombasa",
"Offerings": "Product A",
},
readError: nil,
expectedResult: resource.Result{
Content: fmt.Sprintf(
"Name: %s %s\nGender: %s\nAge: %d\nLocation: %s\nYou provide: %s\n",
"John", "Doe", "Male", 44, "Mombasa", "Product A",
),
},
expectedError: nil,
},
{
name: "Profile with Not Provided Fields",
accountData: map[string]string{
"FirstName": "Not provided",
"FamilyName": "Doe",
"Gender": "Female",
"YOB": "1995",
"Location": "Not provided",
"Offerings": "Service B",
},
readError: nil,
expectedResult: resource.Result{
Content: fmt.Sprintf(
"Name: %s\nGender: %s\nAge: %d\nLocation: %s\nYou provide: %s\n",
"Not provided", "Female", 29, "Not provided", "Service B",
),
},
expectedError: nil,
},
{
name: "Profile with YOB as Not provided",
accountData: map[string]string{
"FirstName": "Not provided",
"FamilyName": "Doe",
"Gender": "Female",
"YOB": "Not provided",
"Location": "Not provided",
"Offerings": "Service B",
},
readError: nil,
expectedResult: resource.Result{
Content: fmt.Sprintf(
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n",
"Not provided", "Female", "Not provided", "Not provided", "Service B",
),
},
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a new instance of MockAccountFileHandler
mockFileHandler := new(mocks.MockAccountFileHandler)
// Set up the mock expectations
mockFileHandler.On("ReadAccountData").Return(tt.accountData, tt.readError)
// Create the Handlers instance with the mock file handler
h := &Handlers{
accountFileHandler: mockFileHandler,
}
// Call the method
result, err := h.GetProfileInfo(context.Background(), "get_profile_info", nil)
// Assert the results
assert.Equal(t, tt.expectedResult, result)
assert.Equal(t, tt.expectedError, err)
// Assert all expectations were met
mockFileHandler.AssertExpectations(t)
})
}
}

View File

@@ -1,44 +0,0 @@
package mocks
import (
"git.grassecon.net/urdt/ussd/internal/models"
"github.com/stretchr/testify/mock"
)
type MockAccountFileHandler struct {
mock.Mock
}
func (m *MockAccountFileHandler) EnsureFileExists() error {
args := m.Called()
return args.Error(0)
}
func (m *MockAccountFileHandler) ReadAccountData() (map[string]string, error) {
args := m.Called()
return args.Get(0).(map[string]string), args.Error(1)
}
func (m *MockAccountFileHandler) WriteAccountData(data map[string]string) error {
args := m.Called(data)
return args.Error(0)
}
type MockAccountService struct {
mock.Mock
}
func (m *MockAccountService) CreateAccount() (*models.AccountResponse, error) {
args := m.Called()
return args.Get(0).(*models.AccountResponse), args.Error(1)
}
func (m *MockAccountService) CheckAccountStatus(TrackingId string) (string, error) {
args := m.Called()
return args.Get(0).(string), args.Error(1)
}
func (m *MockAccountService) CheckBalance(PublicKey string) (string, error) {
args := m.Called()
return args.Get(0).(string), args.Error(1)
}

150
internal/http/server.go Normal file
View File

@@ -0,0 +1,150 @@
package http
import (
"fmt"
"io/ioutil"
"net/http"
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
)
var (
logg = logging.NewVanilla().WithDomain("httpserver")
)
type RequestParser interface {
GetSessionId(rq *http.Request) (string, error)
GetInput(rq *http.Request) ([]byte, error)
}
type DefaultRequestParser struct {
}
func(rp *DefaultRequestParser) GetSessionId(rq *http.Request) (string, error) {
v := rq.Header.Get("X-Vise-Session")
if v == "" {
return "", fmt.Errorf("no session found")
}
return v, nil
}
func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) {
defer rq.Body.Close()
v, err := ioutil.ReadAll(rq.Body)
if err != nil {
return nil, err
}
return v, nil
}
type SessionHandler struct {
cfgTemplate engine.Config
rp RequestParser
rs resource.Resource
//first resource.EntryFunc
hn *ussd.Handlers
provider StorageProvider
}
//func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, first resource.EntryFunc) *SessionHandler {
func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *ussd.Handlers) *SessionHandler {
return &SessionHandler{
cfgTemplate: cfg,
rs: rs,
//first: first,
hn: hn,
rp: rp,
provider: NewSimpleStorageProvider(stateDb, userdataDb),
}
}
func(f *SessionHandler) writeError(w http.ResponseWriter, code int, msg string, err error) {
w.Header().Set("X-Vise", msg + ": " + err.Error())
w.Header().Set("Content-Length", "0")
w.WriteHeader(code)
_, err = w.Write([]byte{})
if err != nil {
w.WriteHeader(500)
w.Header().Set("X-Vise", err.Error())
}
return
}
func(f* SessionHandler) Shutdown() {
err := f.provider.Close()
if err != nil {
logg.Errorf("handler shutdown error", "err", err)
}
}
func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var r bool
sessionId, err := f.rp.GetSessionId(req)
if err != nil {
f.writeError(w, 400, "Session missing", err)
return
}
input, err := f.rp.GetInput(req)
if err != nil {
f.writeError(w, 400, "Input read fail", err)
return
}
ctx := req.Context()
cfg := f.cfgTemplate
cfg.SessionId = sessionId
logg.InfoCtxf(ctx, "new request", "session", cfg.SessionId, "input", input)
storage, err := f.provider.Get(cfg.SessionId)
if err != nil {
f.writeError(w, 500, "Storage retrieval fail", err)
return
}
f.hn = f.hn.WithPersister(storage.Persister)
defer f.provider.Put(cfg.SessionId, storage)
en := getEngine(cfg, f.rs, storage.Persister)
en = en.WithFirst(f.hn.Init)
if cfg.EngineDebug {
en = en.WithDebug(nil)
}
r, err = en.Init(ctx)
if err != nil {
f.writeError(w, 500, "Engine init fail", err)
return
}
if r && len(input) > 0 {
r, err = en.Exec(ctx, input)
}
if err != nil {
f.writeError(w, 500, "Engine exec fail", err)
return
}
w.WriteHeader(200)
w.Header().Set("Content-Type", "text/plain")
_, err = en.WriteResult(ctx, w)
if err != nil {
f.writeError(w, 500, "Write result fail", err)
return
}
err = en.Finish()
if err != nil {
f.writeError(w, 500, "Engine finish fail", err)
return
}
_ = r
}
func getEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) *engine.DefaultEngine {
en := engine.NewEngine(cfg, rs)
en = en.WithPersister(pr)
return en
}

44
internal/http/storage.go Normal file
View File

@@ -0,0 +1,44 @@
package http
import (
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/persist"
)
type Storage struct {
Persister *persist.Persister
UserdataDb db.Db
}
type StorageProvider interface {
Get(sessionId string) (Storage, error)
Put(sessionId string, storage Storage) error
Close() error
}
type SimpleStorageProvider struct {
Storage
}
func NewSimpleStorageProvider(stateStore db.Db, userdataStore db.Db) StorageProvider {
pe := persist.NewPersister(stateStore)
pe = pe.WithFlush()
return &SimpleStorageProvider{
Storage: Storage{
Persister: pe,
UserdataDb: userdataStore,
},
}
}
func (p *SimpleStorageProvider) Get(sessionId string) (Storage, error) {
return p.Storage, nil
}
func (p *SimpleStorageProvider) Put(sessionId string, storage Storage) error {
return nil
}
func (p *SimpleStorageProvider) Close() error {
return p.Storage.UserdataDb.Close()
}

View File

@@ -1,46 +0,0 @@
package utils
import (
"encoding/json"
"os"
)
type AccountFileHandler struct {
FilePath string
}
func NewAccountFileHandler(path string) *AccountFileHandler {
return &AccountFileHandler{FilePath: path}
}
func (afh *AccountFileHandler) ReadAccountData() (map[string]string, error) {
jsonData, err := os.ReadFile(afh.FilePath)
if err != nil {
return nil, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return nil, err
}
return accountData, nil
}
func (afh *AccountFileHandler) WriteAccountData(accountData map[string]string) error {
jsonData, err := json.Marshal(accountData)
if err != nil {
return err
}
return os.WriteFile(afh.FilePath, jsonData, 0644)
}
func (afh *AccountFileHandler) EnsureFileExists() error {
f, err := os.OpenFile(afh.FilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
return f.Close()
}

37
internal/utils/db.go Normal file
View File

@@ -0,0 +1,37 @@
package utils
import (
"encoding/binary"
)
type DataTyp uint16
const (
DATA_ACCOUNT DataTyp = iota
DATA_ACCOUNT_CREATED
DATA_TRACKING_ID
DATA_PUBLIC_KEY
DATA_CUSTODIAL_ID
DATA_ACCOUNT_PIN
DATA_ACCOUNT_STATUS
DATA_FIRST_NAME
DATA_FAMILY_NAME
DATA_YOB
DATA_LOCATION
DATA_GENDER
DATA_OFFERINGS
DATA_RECIPIENT
DATA_AMOUNT
DATA_TEMPORARY_PIN
)
func typToBytes(typ DataTyp) []byte {
var b [2]byte
binary.BigEndian.PutUint16(b[:], uint16(typ))
return b[:]
}
func PackKey(typ DataTyp, data []byte) []byte {
v := typToBytes(typ)
return append(v, data...)
}

View File

@@ -1,13 +0,0 @@
package utils
type AccountFileHandlerInterface interface {
EnsureFileExists() error
ReadAccountData() (map[string]string, error)
WriteAccountData(data map[string]string) error
}

View File

@@ -0,0 +1,32 @@
package utils
import (
"context"
"git.defalsify.org/vise.git/db"
)
type DataStore interface {
db.Db
ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error)
WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error
}
type UserDataStore struct {
db.Db
}
// ReadEntry retrieves an entry from the store based on the provided parameters.
func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) {
store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId)
k := PackKey(typ, []byte(sessionId))
return store.Get(ctx, k)
}
func (store *UserDataStore) WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error {
store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId)
k := PackKey(typ, []byte(sessionId))
return store.Put(ctx, k, value)
}

View File

@@ -4,7 +4,7 @@ TXTS = $(wildcard ./*.txt.orig)
# Rule to build .bin files from .vis files # Rule to build .bin files from .vis files
%.vis: %.vis:
go run ../../go-vise/dev/asm -f pp.csv $(basename $@).vis > $(basename $@).bin go run ../../go-vise/dev/asm/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin
@echo "Built $(basename $@).bin from $(basename $@).vis" @echo "Built $(basename $@).bin from $(basename $@).vis"
# Rule to copy .orig files to .txt # Rule to copy .orig files to .txt

View File

@@ -0,0 +1 @@
Confirm your new PIN:

View File

@@ -0,0 +1,7 @@
LOAD verify_pin 0
MOUT back 0
HALT
RELOAD verify_pin
CATCH create_pin_mismatch flag_pin_mismatch 1
MOVE pin_reset_success
INCMP _ 0

View File

@@ -0,0 +1,2 @@
LOAD quit_with_help 0
HALT

View File

@@ -0,0 +1 @@
The PIN you entered is invalid.The PIN must be different from your current PIN.For help call +254757628885

View File

@@ -0,0 +1,4 @@
MOUT back 0
HALT
INCMP _ 0

View File

@@ -6,3 +6,6 @@ msgstr "Ombi lako limetumwa. %s atapokea %s kutoka kwa %s."
msgid "Thank you for using Sarafu. Goodbye!" msgid "Thank you for using Sarafu. Goodbye!"
msgstr "Asante kwa kutumia huduma ya Sarafu. Kwaheri!" msgstr "Asante kwa kutumia huduma ya Sarafu. Kwaheri!"
msgid "For more help,please call: 0757628885"
msgstr "Kwa usaidizi zaidi,piga: 0757628885"

View File

@@ -10,6 +10,6 @@ HALT
INCMP send 1 INCMP send 1
INCMP quit 2 INCMP quit 2
INCMP my_account 3 INCMP my_account 3
INCMP quit 4 INCMP help 4
INCMP quit 9 INCMP quit 9
INCMP . * INCMP . *

View File

@@ -1,3 +1,4 @@
LOAD authorize_account 6
MAP validate_amount MAP validate_amount
RELOAD get_recipient RELOAD get_recipient
MAP get_recipient MAP get_recipient
@@ -6,9 +7,9 @@ MAP get_sender
MOUT back 0 MOUT back 0
MOUT quit 9 MOUT quit 9
HALT HALT
LOAD authorize_account 6
RELOAD authorize_account RELOAD authorize_account
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH transaction_initiated flag_account_authorized 1
INCMP _ 0 INCMP _ 0
INCMP quit 9 INCMP quit 9
INCMP transaction_initiated *