Compare commits

..

1 Commits

Author SHA1 Message Date
Carlosokumu
95bf8252b4 setup a test for get profile info 2024-08-30 15:51:49 +03:00
41 changed files with 1532 additions and 1325 deletions

View File

@@ -1,215 +0,0 @@
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

@@ -7,192 +7,137 @@ import (
"os" "os"
"path" "path"
"git.defalsify.org/vise.git/asm" "git.defalsify.org/vise.git/cache"
"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/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"
"git.grassecon.net/urdt/ussd/internal/models"
) )
var ( var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
) )
func getParser(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, pe *persist.Persister, userdataStore db.Db) (*ussd.Handlers, error) {
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 dbDir string var dir string
var root string
var size uint var size uint
var sessionId string var sessionId string
var debug bool flag.StringVar(&dir, "d", ".", "resource dir to read from")
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.UintVar(&size, "s", 0, "max size of output")
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") flag.StringVar(&root, "root", "root", "entry point symbol")
flag.BoolVar(&debug, "d", false, "use engine debug output") flag.StringVar(&sessionId, "session-id", "default", "session id")
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) st := state.NewState(16)
pfp := path.Join(scriptDir, "pp.csv") st.UseDebug()
flagParser, err := getParser(pfp, true) state.FlagDebugger.Register(models.USERFLAG_LANGUAGE_SET, "LANGUAGE_CHANGE")
state.FlagDebugger.Register(models.USERFLAG_ACCOUNT_CREATED, "ACCOUNT_CREATED")
if err != nil { state.FlagDebugger.Register(models.USERFLAG_ACCOUNT_SUCCESS, "ACCOUNT_SUCCESS")
os.Exit(1) state.FlagDebugger.Register(models.USERFLAG_ACCOUNT_PENDING, "ACCOUNT_PENDING")
} state.FlagDebugger.Register(models.USERFLAG_INCORRECTPIN, "INCORRECTPIN")
state.FlagDebugger.Register(models.USERFLAG_INCORRECTDATEFORMAT, "INVALIDDATEFORMAT")
state.FlagDebugger.Register(models.USERFLAG_INVALID_RECIPIENT, "INVALIDRECIPIENT")
state.FlagDebugger.Register(models.USERFLAG_PINMISMATCH, "PINMISMATCH")
state.FlagDebugger.Register(models.USERFLAG_PIN_SET, "PIN_SET")
state.FlagDebugger.Register(models.USERFLAG_INVALID_RECIPIENT_WITH_INVITE, "INVALIDRECIPIENT_WITH_INVITE")
state.FlagDebugger.Register(models.USERFLAG_INVALID_AMOUNT, "INVALIDAMOUNT")
state.FlagDebugger.Register(models.USERFLAG_ALLOW_UPDATE, "UNLOCKFORUPDATE")
state.FlagDebugger.Register(models.USERFLAG_VALIDPIN, "VALIDPIN")
state.FlagDebugger.Register(models.USERFLAG_VALIDPIN, "ACCOUNTUNLOCKED")
state.FlagDebugger.Register(models.USERFLAG_ACCOUNT_CREATION_FAILED, "ACCOUNT_CREATION_FAILED")
state.FlagDebugger.Register(models.USERFLAG_SINGLE_EDIT, "SINGLEEDIT")
rfs := resource.NewFsResource(scriptDir)
ca := cache.NewCache()
cfg := engine.Config{ cfg := engine.Config{
Root: "root", Root: "root",
SessionId: sessionId, SessionId: sessionId,
OutputSize: uint32(size),
FlagCount: uint32(16),
} }
rs, err := getResource(scriptDir, ctx) dp := path.Join(scriptDir, ".state")
err := os.MkdirAll(dp, 0700)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, "state dir create exited with error: %v\n", err)
os.Exit(1) os.Exit(1)
} }
pr := persist.NewFsPersister(dp)
en, err := engine.NewPersistedEngine(ctx, cfg, pr, rfs)
pe, err := getPersister(dbDir, ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) pr = pr.WithContent(&st, ca)
os.Exit(1) err = pr.Save(cfg.SessionId)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to save state with error: %v\n", err)
}
en, err = engine.NewPersistedEngine(ctx, cfg, pr, rfs)
if err != nil {
fmt.Fprintf(os.Stderr, "engine create exited with error: %v\n", err)
os.Exit(1)
}
} }
store := getUserdataDb(dbDir, ctx) fp := path.Join(dp, sessionId)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
dbResource, ok := rs.(*resource.DbResource) ussdHandlers := ussd.NewHandlers(fp, &st)
if !ok {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
hl, err := getHandler(flagParser, dbResource, pe, store) rfs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
if err != nil { rfs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
fmt.Fprintf(os.Stderr, err.Error()) rfs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
os.Exit(1) rfs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
} 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)
en := getEngine(cfg, rs, pe) cont, err := en.Init(ctx)
en = en.WithFirst(hl.Init) en.SetDebugger(engine.NewSimpleDebug(nil))
if debug {
en = en.WithDebug(nil)
}
_, err = en.Init(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "engine init exited with error: %v\n", err) fmt.Fprintf(os.Stderr, "engine init exited with error: %v\n", err)
os.Exit(1) os.Exit(1)
} }
if !cont {
_, err = en.WriteResult(ctx, os.Stdout)
if err != nil {
fmt.Fprintf(os.Stderr, "dead init write error: %v\n", err)
os.Exit(1)
}
err = en.Finish()
if err != nil {
fmt.Fprintf(os.Stderr, "engine finish error: %v\n", err)
os.Exit(1)
}
os.Stdout.Write([]byte{0x0a})
os.Exit(0)
}
err = engine.Loop(ctx, en, os.Stdin, os.Stdout) err = engine.Loop(ctx, en, os.Stdin, os.Stdout)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err) fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err)

1
go-vise Submodule

Submodule go-vise added at 1f47a674d9

14
go.mod
View File

@@ -2,16 +2,4 @@ module git.grassecon.net/urdt/ussd
go 1.22.6 go 1.22.6
require ( require github.com/stretchr/testify v1.9.0 // indirect
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,24 +1,2 @@
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7 h1:embPZDx0Sgpq6jp9vcZ1GVI0eum3PsPCmAfxAa/1KLI= 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/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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,9 +16,10 @@ 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:
@@ -26,12 +27,14 @@ 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 := as.Client.Get(config.TrackStatusURL + trackingId) resp, err := http.Get(config.TrackStatusURL + trackingId)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -47,15 +50,18 @@ 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
@@ -77,14 +83,15 @@ 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 := as.Client.Post(config.CreateAccountURL, "application/json", nil) resp, err := http.Post(config.CreateAccountURL, "application/json", nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -100,5 +107,6 @@ 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

@@ -0,0 +1,929 @@
package ussd
import (
"context"
"encoding/json"
"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
}
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)
// Initialize Handlers with mock account service
h := &Handlers{
fs: &FSData{Path: accountFilePath},
accountFileHandler: accountFileHandler,
accountService: mockAccountService,
}
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{models.USERFLAG_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)
h := &Handlers{
accountFileHandler: accountFileHandler,
}
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{models.USERFLAG_INCORRECTPIN},
expectedData: initialAccountData, // No changes expected
expectedErrors: false,
},
{
name: "Invalid PIN - less than 4 digits",
input: []byte("123"),
expectedFlags: []uint32{models.USERFLAG_INCORRECTPIN},
expectedData: initialAccountData, // No changes expected
expectedErrors: false,
},
{
name: "Invalid PIN - more than 4 digits",
input: []byte("12345"),
expectedFlags: []uint32{models.USERFLAG_INCORRECTPIN},
expectedData: initialAccountData, // No changes expected
expectedErrors: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Ensure the file exists before running the test
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) {
// Create a new instance of MockAccountFileHandler
mockFileHandler := new(mocks.MockAccountFileHandler)
// Define test cases
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": "Bananas",
},
readError: nil,
expectedResult: resource.Result{
Content: "Name: John Doe\nGender: Male\nAge: 44\nLocation: Mombasa\nYou provide: Bananas\n",
},
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 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

@@ -0,0 +1,44 @@
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)
}

View File

@@ -1,150 +0,0 @@
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
}

View File

@@ -1,44 +0,0 @@
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()
}

22
internal/models/flags.go Normal file
View File

@@ -0,0 +1,22 @@
package models
import "git.defalsify.org/vise.git/state"
const (
USERFLAG_LANGUAGE_SET = iota + state.FLAG_USERSTART
USERFLAG_ACCOUNT_CREATED
USERFLAG_ACCOUNT_PENDING
USERFLAG_ACCOUNT_SUCCESS
USERFLAG_ACCOUNT_AUTHORIZED
USERFLAG_INVALID_RECIPIENT
USERFLAG_INVALID_RECIPIENT_WITH_INVITE
USERFLAG_INCORRECTPIN
USERFLAG_ALLOW_UPDATE
USERFLAG_INVALID_AMOUNT
USERFLAG_PIN_SET
USERFLAG_VALIDPIN
USERFLAG_PINMISMATCH
USERFLAG_INCORRECTDATEFORMAT
USERFLAG_ACCOUNT_CREATION_FAILED
USERFLAG_SINGLE_EDIT
)

View File

@@ -0,0 +1,46 @@
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()
}

View File

@@ -1,37 +0,0 @@
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

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

View File

@@ -1,32 +0,0 @@
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/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin go run ../../go-vise/dev/asm $(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

@@ -1,4 +1,4 @@
RELOAD verify_pin RELOAD verify_pin
CATCH create_pin_mismatch flag_pin_mismatch 1 CATCH create_pin_mismatch 20 1
LOAD quit 0 LOAD quit 0
HALT HALT

View File

@@ -1,3 +1,3 @@
RELOAD check_account_status RELOAD check_account_status
CATCH main flag_account_success 1 CATCH main 11 1
HALT HALT

View File

@@ -5,7 +5,7 @@ MOUT back 0
HALT HALT
LOAD validate_amount 64 LOAD validate_amount 64
RELOAD validate_amount RELOAD validate_amount
CATCH invalid_amount flag_invalid_amount 1 CATCH invalid_amount 17 1
INCMP _ 0 INCMP _ 0
LOAD get_recipient 12 LOAD get_recipient 12
LOAD get_sender 64 LOAD get_sender 64

View File

@@ -1,4 +1,4 @@
LOAD reset_account_authorized 0 LOAD reset_unlocked 0
MOUT my_balance 1 MOUT my_balance 1
MOUT community_balance 2 MOUT community_balance 2
MOUT back 0 MOUT back 0

View File

@@ -1,5 +1,5 @@
LOAD reset_incorrect 0 LOAD reset_incorrect 0
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin 15 1
CATCH pin_entry flag_account_authorized 0 CATCH pin_entry 12 0
LOAD quit_with_balance 0 LOAD quit_with_balance 0
HALT HALT

View File

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

View File

@@ -1,7 +0,0 @@
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

@@ -1,9 +1,9 @@
LOAD create_account 0 LOAD create_account 0
CATCH account_creation_failed flag_account_creation_failed 1 CATCH account_creation_failed 22 1
MOUT exit 0 MOUT exit 0
HALT HALT
LOAD save_pin 0 LOAD save_pin 0
RELOAD save_pin RELOAD save_pin
CATCH . flag_incorrect_pin 1 CATCH . 15 1
INCMP quit 0 INCMP quit 0
INCMP confirm_create_pin * INCMP confirm_create_pin *

View File

@@ -1,9 +1,12 @@
CATCH incorrect_date_format flag_incorrect_date_format 1 CATCH incorrect_date_format 21 1
LOAD save_yob 0 LOAD save_yob 0
CATCH update_success flag_allow_update 1 CATCH update_success 16 1
MOUT back 0 MOUT back 0
HALT HALT
INCMP _ 0 INCMP _ 0
LOAD save_location 0 LOAD save_location 0
CATCH pin_entry flag_single_edit 1 CATCH pin_entry 23 1
INCMP enter_offerings * INCMP enter_offerings *

View File

@@ -1,6 +1,6 @@
LOAD save_location 0 LOAD save_location 0
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin 15 1
CATCH update_success flag_allow_update 1 CATCH update_success 16 1
MOUT back 0 MOUT back 0
HALT HALT
LOAD save_offerings 0 LOAD save_offerings 0

View File

@@ -1,9 +1,9 @@
LOAD save_gender 0 LOAD save_gender 0
CATCH update_success flag_allow_update 1 CATCH update_success 16 1
MOUT back 0 MOUT back 0
HALT HALT
INCMP _ 0 INCMP _ 0
LOAD verify_yob 8 LOAD verify_yob 8
LOAD save_yob 0 LOAD save_yob 0
CATCH pin_entry flag_single_edit 1 CATCH pin_entry 23 1
INCMP enter_location * INCMP enter_location *

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,3 @@ 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 help 4 INCMP quit 4
INCMP quit 9 INCMP quit 9
INCMP . * INCMP . *

View File

@@ -1,5 +1,5 @@
LOAD reset_incorrect 0 LOAD reset_incorrect 0
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin 15 1
CATCH pin_entry flag_account_authorized 0 CATCH pin_entry 12 0
LOAD quit_with_balance 0 LOAD quit_with_balance 0
HALT HALT

View File

@@ -1,16 +0,0 @@
flag,flag_language_set,8,checks whether the user has set their prefered language
flag,flag_account_created,9,this is set when an account has been created on the API
flag,flag_account_creation_failed,10,this is set when there's an error from the API during account creation
flag,flag_account_pending,11,this is set when an account does not have a status of SUCCESS
flag,flag_account_success,12,this is set when an account has a status of SUCCESS
flag,flag_pin_mismatch,13,this is set when the confirmation PIN matches the initial PIN during registration
flag,flag_pin_set,14,this is set when a newly registered user sets a PIN. This must be present for an account to access the main menu
flag,flag_account_authorized,15,this is set to allow a user access guarded nodes after providing a correct PIN
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_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
flag,flag_incorrect_date_format,23,this is set when the given year of birth is invalid
1 flag flag_language_set 8 checks whether the user has set their prefered language
2 flag flag_account_created 9 this is set when an account has been created on the API
3 flag flag_account_creation_failed 10 this is set when there's an error from the API during account creation
4 flag flag_account_pending 11 this is set when an account does not have a status of SUCCESS
5 flag flag_account_success 12 this is set when an account has a status of SUCCESS
6 flag flag_pin_mismatch 13 this is set when the confirmation PIN matches the initial PIN during registration
7 flag flag_pin_set 14 this is set when a newly registered user sets a PIN. This must be present for an account to access the main menu
8 flag flag_account_authorized 15 this is set to allow a user access guarded nodes after providing a correct PIN
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
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
16 flag flag_incorrect_date_format 23 this is set when the given year of birth is invalid

View File

@@ -1,7 +1,7 @@
CATCH select_language flag_language_set 0 CATCH select_language 8 0
CATCH terms flag_account_created 0 CATCH terms 9 0
LOAD check_account_status 0 LOAD check_account_status 0
CATCH account_pending flag_account_pending 1 CATCH account_pending 10 1
CATCH create_pin flag_pin_set 0 CATCH create_pin 18 0
CATCH main flag_account_success 1 CATCH main 11 1
HALT HALT

View File

@@ -1,12 +1,12 @@
LOAD save_familyname 0 LOAD save_familyname 0
CATCH update_success flag_allow_update 1 CATCH update_success 16 1
MOUT male 1 MOUT male 1
MOUT female 2 MOUT female 2
MOUT unspecified 3 MOUT unspecified 3
MOUT back 0 MOUT back 0
HALT HALT
LOAD save_gender 0 LOAD save_gender 0
CATCH pin_entry flag_single_edit 1 CATCH pin_entry 23 1
INCMP _ 0 INCMP _ 0
INCMP enter_yob 1 INCMP enter_yob 1
INCMP enter_yob 2 INCMP enter_yob 2

View File

@@ -3,6 +3,6 @@ MOUT back 0
HALT HALT
LOAD validate_recipient 20 LOAD validate_recipient 20
RELOAD validate_recipient RELOAD validate_recipient
CATCH invalid_recipient flag_invalid_recipient 1 CATCH invalid_recipient 13 1
INCMP _ 0 INCMP _ 0
INCMP amount * INCMP amount *

View File

@@ -1,6 +1,4 @@
LOAD reset_incorrect 6 LOAD reset_incorrect 0
CATCH incorrect_pin flag_incorrect_pin 1
CATCH _ flag_account_authorized 0
LOAD get_amount 10 LOAD get_amount 10
MAP get_amount MAP get_amount
RELOAD get_recipient RELOAD get_recipient

View File

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

View File

@@ -1,8 +1,8 @@
LOAD get_profile_info 0 LOAD get_profile_info 0
MAP get_profile_info MAP get_profile_info
LOAD reset_incorrect 6 LOAD reset_incorrect 0
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin 15 1
CATCH pin_entry flag_account_authorized 0 CATCH pin_entry 12 0
MOUT back 0 MOUT back 0
HALT HALT
INCMP _ 0 INCMP _ 0