Include local handlers code
This commit is contained in:
commit
67eb6d6597
34
args/lang.go
Normal file
34
args/lang.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package args
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/lang"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LangVar struct {
|
||||||
|
v []lang.Language
|
||||||
|
}
|
||||||
|
|
||||||
|
func(lv *LangVar) Set(s string) error {
|
||||||
|
v, err := lang.LanguageFromCode(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lv.v = append(lv.v, v)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func(lv *LangVar) String() string {
|
||||||
|
var s []string
|
||||||
|
for _, v := range(lv.v) {
|
||||||
|
s = append(s, v.Code)
|
||||||
|
}
|
||||||
|
return strings.Join(s, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func(lv *LangVar) Langs() []lang.Language {
|
||||||
|
return lv.v
|
||||||
|
}
|
||||||
|
|
||||||
|
|
152
cmd/main.go
Normal file
152
cmd/main.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/engine"
|
||||||
|
"git.defalsify.org/vise.git/logging"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.defalsify.org/vise.git/lang"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/config"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/initializers"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/remote"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/common"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logg = logging.NewVanilla()
|
||||||
|
scriptDir = path.Join("services", "registration")
|
||||||
|
menuSeparator = ": "
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
initializers.LoadEnvVariables()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: external script automatically generate language handler list from select language vise code OR consider dynamic menu generation script possibility
|
||||||
|
func main() {
|
||||||
|
config.LoadConfig()
|
||||||
|
|
||||||
|
var connStr string
|
||||||
|
var size uint
|
||||||
|
var sessionId string
|
||||||
|
var database string
|
||||||
|
var engineDebug bool
|
||||||
|
var resourceDir string
|
||||||
|
var err error
|
||||||
|
var gettextDir string
|
||||||
|
var langs args.LangVar
|
||||||
|
|
||||||
|
flag.StringVar(&resourceDir, "resourcedir", scriptDir, "resource dir")
|
||||||
|
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||||
|
flag.StringVar(&connStr, "c", "", "connection string")
|
||||||
|
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||||
|
flag.UintVar(&size, "s", 160, "max size of output")
|
||||||
|
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
||||||
|
flag.Var(&langs, "language", "add symbol resolution for language")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if connStr != "" {
|
||||||
|
connStr = config.DbConn
|
||||||
|
}
|
||||||
|
connData, err := common.ToConnData(connStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
logg.Infof("start command", "conn", connData, "outputsize", size)
|
||||||
|
|
||||||
|
if len(langs.Langs()) == 0 {
|
||||||
|
langs.Set(config.DefaultLanguage)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
ctx = context.WithValue(ctx, "Database", database)
|
||||||
|
|
||||||
|
ln, err := lang.LanguageFromCode(config.DefaultLanguage)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "default language set error: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
ctx = context.WithValue(ctx, "Language", ln)
|
||||||
|
|
||||||
|
pfp := path.Join(scriptDir, "pp.csv")
|
||||||
|
|
||||||
|
cfg := engine.Config{
|
||||||
|
Root: "root",
|
||||||
|
SessionId: sessionId,
|
||||||
|
OutputSize: uint32(size),
|
||||||
|
FlagCount: uint32(128),
|
||||||
|
MenuSeparator: menuSeparator,
|
||||||
|
}
|
||||||
|
|
||||||
|
menuStorageService, err := common.NewStorageService(connData)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "menu storage service error: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gettextDir != "" {
|
||||||
|
menuStorageService = menuStorageService.WithGettext(gettextDir, langs.Langs())
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := menuStorageService.GetResource(ctx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pe, err := menuStorageService.GetPersister(ctx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
userdatastore, err := menuStorageService.GetUserdataDb(ctx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbResource, ok := rs.(*resource.DbResource)
|
||||||
|
if !ok {
|
||||||
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
||||||
|
lhs.SetDataStore(&userdatastore)
|
||||||
|
lhs.SetPersister(pe)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
accountService := remote.AccountService{}
|
||||||
|
hl, err := lhs.GetHandler(&accountService)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
en := lhs.GetEngine()
|
||||||
|
en = en.WithFirst(hl.Init)
|
||||||
|
if engineDebug {
|
||||||
|
en = en.WithDebug(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = engine.Loop(ctx, en, os.Stdin, os.Stdout, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
37
go.mod
Normal file
37
go.mod
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
module git.grassecon.net/grassrootseconomics/sarafu-vise
|
||||||
|
|
||||||
|
go 1.23.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d
|
||||||
|
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250110120337-a7a8a482ab05
|
||||||
|
github.com/alecthomas/assert/v2 v2.2.2
|
||||||
|
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta
|
||||||
|
github.com/peteole/testdata-loader v0.3.0
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/alecthomas/participle/v2 v2.0.0 // indirect
|
||||||
|
github.com/alecthomas/repr v0.2.0 // indirect
|
||||||
|
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||||
|
github.com/grassrootseconomics/eth-custodial v1.3.0-beta // indirect
|
||||||
|
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
|
||||||
|
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
|
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
golang.org/x/crypto v0.27.0 // indirect
|
||||||
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
|
golang.org/x/text v0.18.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
73
go.sum
Normal file
73
go.sum
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d h1:bPAOVZOX4frSGhfOdcj7kc555f8dc9DmMd2YAyC2AMw=
|
||||||
|
git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
|
||||||
|
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250110120337-a7a8a482ab05 h1:HZ9Di5Ui80vEnIwu2nU7pCH5V6AT25b9JmYy7af9CvA=
|
||||||
|
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250110120337-a7a8a482ab05/go.mod h1:E6W7ZOa7ZvVr0Bc5ot0LNSwpSPYq4hXlAIvEPy3AJ7U=
|
||||||
|
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU=
|
||||||
|
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
|
||||||
|
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta h1:fn1gwbWIwHVEBtUC2zi5OqTlfI/5gU1SMk0fgGixIXk=
|
||||||
|
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta/go.mod h1:omfI0QtUwIdpu9gMcUqLMCG8O1XWjqJGBx1qUMiGWC0=
|
||||||
|
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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||||
|
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
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/pashagolub/pgxmock/v4 v4.3.0 h1:DqT7fk0OCK6H0GvqtcMsLpv8cIwWqdxWgfZNLeHCb/s=
|
||||||
|
github.com/pashagolub/pgxmock/v4 v4.3.0/go.mod h1:9VoVHXwS3XR/yPtKGzwQvwZX1kzGB9sM8SviDcHDa3A=
|
||||||
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
|
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||||
|
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||||
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||||
|
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
|
||||||
|
gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
2157
handlers/application/menuhandler.go
Normal file
2157
handlers/application/menuhandler.go
Normal file
File diff suppressed because it is too large
Load Diff
2331
handlers/application/menuhandler_test.go
Normal file
2331
handlers/application/menuhandler_test.go
Normal file
File diff suppressed because it is too large
Load Diff
141
handlers/local.go
Normal file
141
handlers/local.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/asm"
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/engine"
|
||||||
|
"git.defalsify.org/vise.git/persist"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/utils"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/remote"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HandlerService interface {
|
||||||
|
GetHandler() (*application.Handlers, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocalHandlerService struct {
|
||||||
|
Parser *asm.FlagParser
|
||||||
|
DbRs *resource.DbResource
|
||||||
|
Pe *persist.Persister
|
||||||
|
UserdataStore *db.Db
|
||||||
|
AdminStore *utils.AdminStore
|
||||||
|
Cfg engine.Config
|
||||||
|
Rs resource.Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLocalHandlerService(ctx context.Context, fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) {
|
||||||
|
parser, err := getParser(fp, debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
adminstore, err := utils.NewAdminStore(ctx, "admin_numbers")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &LocalHandlerService{
|
||||||
|
Parser: parser,
|
||||||
|
DbRs: dbResource,
|
||||||
|
AdminStore: adminstore,
|
||||||
|
Cfg: cfg,
|
||||||
|
Rs: rs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LocalHandlerService) SetPersister(Pe *persist.Persister) {
|
||||||
|
ls.Pe = Pe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LocalHandlerService) SetDataStore(db *db.Db) {
|
||||||
|
ls.UserdataStore = db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceInterface) (*application.Handlers, error) {
|
||||||
|
replaceSeparatorFunc := func(input string) string {
|
||||||
|
return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
appHandlers, err := application.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService, replaceSeparatorFunc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
appHandlers = appHandlers.WithPersister(ls.Pe)
|
||||||
|
ls.DbRs.AddLocalFunc("set_language", appHandlers.SetLanguage)
|
||||||
|
ls.DbRs.AddLocalFunc("create_account", appHandlers.CreateAccount)
|
||||||
|
ls.DbRs.AddLocalFunc("save_temporary_pin", appHandlers.SaveTemporaryPin)
|
||||||
|
ls.DbRs.AddLocalFunc("verify_create_pin", appHandlers.VerifyCreatePin)
|
||||||
|
ls.DbRs.AddLocalFunc("check_identifier", appHandlers.CheckIdentifier)
|
||||||
|
ls.DbRs.AddLocalFunc("check_account_status", appHandlers.CheckAccountStatus)
|
||||||
|
ls.DbRs.AddLocalFunc("authorize_account", appHandlers.Authorize)
|
||||||
|
ls.DbRs.AddLocalFunc("quit", appHandlers.Quit)
|
||||||
|
ls.DbRs.AddLocalFunc("check_balance", appHandlers.CheckBalance)
|
||||||
|
ls.DbRs.AddLocalFunc("validate_recipient", appHandlers.ValidateRecipient)
|
||||||
|
ls.DbRs.AddLocalFunc("transaction_reset", appHandlers.TransactionReset)
|
||||||
|
ls.DbRs.AddLocalFunc("invite_valid_recipient", appHandlers.InviteValidRecipient)
|
||||||
|
ls.DbRs.AddLocalFunc("max_amount", appHandlers.MaxAmount)
|
||||||
|
ls.DbRs.AddLocalFunc("validate_amount", appHandlers.ValidateAmount)
|
||||||
|
ls.DbRs.AddLocalFunc("reset_transaction_amount", appHandlers.ResetTransactionAmount)
|
||||||
|
ls.DbRs.AddLocalFunc("get_recipient", appHandlers.GetRecipient)
|
||||||
|
ls.DbRs.AddLocalFunc("get_sender", appHandlers.GetSender)
|
||||||
|
ls.DbRs.AddLocalFunc("get_amount", appHandlers.GetAmount)
|
||||||
|
ls.DbRs.AddLocalFunc("reset_incorrect", appHandlers.ResetIncorrectPin)
|
||||||
|
ls.DbRs.AddLocalFunc("save_firstname", appHandlers.SaveFirstname)
|
||||||
|
ls.DbRs.AddLocalFunc("save_familyname", appHandlers.SaveFamilyname)
|
||||||
|
ls.DbRs.AddLocalFunc("save_gender", appHandlers.SaveGender)
|
||||||
|
ls.DbRs.AddLocalFunc("save_location", appHandlers.SaveLocation)
|
||||||
|
ls.DbRs.AddLocalFunc("save_yob", appHandlers.SaveYob)
|
||||||
|
ls.DbRs.AddLocalFunc("save_offerings", appHandlers.SaveOfferings)
|
||||||
|
ls.DbRs.AddLocalFunc("reset_account_authorized", appHandlers.ResetAccountAuthorized)
|
||||||
|
ls.DbRs.AddLocalFunc("reset_allow_update", appHandlers.ResetAllowUpdate)
|
||||||
|
ls.DbRs.AddLocalFunc("get_profile_info", appHandlers.GetProfileInfo)
|
||||||
|
ls.DbRs.AddLocalFunc("verify_yob", appHandlers.VerifyYob)
|
||||||
|
ls.DbRs.AddLocalFunc("reset_incorrect_date_format", appHandlers.ResetIncorrectYob)
|
||||||
|
ls.DbRs.AddLocalFunc("initiate_transaction", appHandlers.InitiateTransaction)
|
||||||
|
ls.DbRs.AddLocalFunc("verify_new_pin", appHandlers.VerifyNewPin)
|
||||||
|
ls.DbRs.AddLocalFunc("confirm_pin_change", appHandlers.ConfirmPinChange)
|
||||||
|
ls.DbRs.AddLocalFunc("quit_with_help", appHandlers.QuitWithHelp)
|
||||||
|
ls.DbRs.AddLocalFunc("fetch_community_balance", appHandlers.FetchCommunityBalance)
|
||||||
|
ls.DbRs.AddLocalFunc("set_default_voucher", appHandlers.SetDefaultVoucher)
|
||||||
|
ls.DbRs.AddLocalFunc("check_vouchers", appHandlers.CheckVouchers)
|
||||||
|
ls.DbRs.AddLocalFunc("get_vouchers", appHandlers.GetVoucherList)
|
||||||
|
ls.DbRs.AddLocalFunc("view_voucher", appHandlers.ViewVoucher)
|
||||||
|
ls.DbRs.AddLocalFunc("set_voucher", appHandlers.SetVoucher)
|
||||||
|
ls.DbRs.AddLocalFunc("get_voucher_details", appHandlers.GetVoucherDetails)
|
||||||
|
ls.DbRs.AddLocalFunc("reset_valid_pin", appHandlers.ResetValidPin)
|
||||||
|
ls.DbRs.AddLocalFunc("check_pin_mismatch", appHandlers.CheckBlockedNumPinMisMatch)
|
||||||
|
ls.DbRs.AddLocalFunc("validate_blocked_number", appHandlers.ValidateBlockedNumber)
|
||||||
|
ls.DbRs.AddLocalFunc("retrieve_blocked_number", appHandlers.RetrieveBlockedNumber)
|
||||||
|
ls.DbRs.AddLocalFunc("reset_unregistered_number", appHandlers.ResetUnregisteredNumber)
|
||||||
|
ls.DbRs.AddLocalFunc("reset_others_pin", appHandlers.ResetOthersPin)
|
||||||
|
ls.DbRs.AddLocalFunc("save_others_temporary_pin", appHandlers.SaveOthersTemporaryPin)
|
||||||
|
ls.DbRs.AddLocalFunc("get_current_profile_info", appHandlers.GetCurrentProfileInfo)
|
||||||
|
ls.DbRs.AddLocalFunc("check_transactions", appHandlers.CheckTransactions)
|
||||||
|
ls.DbRs.AddLocalFunc("get_transactions", appHandlers.GetTransactionsList)
|
||||||
|
ls.DbRs.AddLocalFunc("view_statement", appHandlers.ViewTransactionStatement)
|
||||||
|
ls.DbRs.AddLocalFunc("update_all_profile_items", appHandlers.UpdateAllProfileItems)
|
||||||
|
ls.DbRs.AddLocalFunc("set_back", appHandlers.SetBack)
|
||||||
|
ls.DbRs.AddLocalFunc("show_blocked_account", appHandlers.ShowBlockedAccount)
|
||||||
|
|
||||||
|
return appHandlers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: enable setting of sessionId on engine init time
|
||||||
|
func (ls *LocalHandlerService) GetEngine() *engine.DefaultEngine {
|
||||||
|
en := engine.NewEngine(ls.Cfg, ls.Rs)
|
||||||
|
en = en.WithPersister(ls.Pe)
|
||||||
|
return en
|
||||||
|
}
|
443
menutraversal_test/group_test.json
Normal file
443
menutraversal_test/group_test.json
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
{
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "my_account_change_pin",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "5",
|
||||||
|
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter your old PIN\n\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Enter a new four number PIN:\n\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Confirm your new PIN:\n\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Your PIN change request has been successful\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_language_change",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1235",
|
||||||
|
"expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Select language:\n1:English\n2:Kiswahili"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Your language change request was successful.\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_check_my_balance",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1235",
|
||||||
|
"expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Balance: {balance}\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_check_community_balance",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1235",
|
||||||
|
"expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "{balance}\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_all_account_details_starting_from_firstname",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter your first names:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "foo",
|
||||||
|
"expectedContent": "Enter family name:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "bar",
|
||||||
|
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter your year of birth\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1940",
|
||||||
|
"expectedContent": "Enter your location:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Kilifi",
|
||||||
|
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Bananas",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_familyname_when_all_account__details_have_been_set",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "Enter family name:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "bar",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_gender_when_all_account__details_have_been_set",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_yob_when_all_account__details_have_been_set",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "4",
|
||||||
|
"expectedContent": "Enter your year of birth\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1945",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_location_when_all_account_details_have_been_set",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "5",
|
||||||
|
"expectedContent": "Enter your location:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Kilifi",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_offerings_when_all_account__details_have_been_set",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "6",
|
||||||
|
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Bananas",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_view_profile",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "7",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 80\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
386
menutraversal_test/menu_traversal_test.go
Normal file
386
menutraversal_test/menu_traversal_test.go
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
package menutraversaltest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/testutil"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/testutil/driver"
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testData = driver.ReadData()
|
||||||
|
sessionID string
|
||||||
|
src = rand.NewSource(42)
|
||||||
|
g = rand.New(src)
|
||||||
|
)
|
||||||
|
|
||||||
|
var groupTestFile = flag.String("test-file", "group_test.json", "The test file to use for running the group tests")
|
||||||
|
var database = flag.String("db", "gdbm", "Specify the database (gdbm or postgres)")
|
||||||
|
var connStr = flag.String("conn", ".test_state", "connection string")
|
||||||
|
var dbSchema = flag.String("schema", "test", "Specify the database schema (default test)")
|
||||||
|
|
||||||
|
func GenerateSessionId() string {
|
||||||
|
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
|
||||||
|
v, err := uu.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the public key from the engine response
|
||||||
|
func extractPublicKey(response []byte) string {
|
||||||
|
// Regex pattern to match the public key starting with 0x and 40 characters
|
||||||
|
re := regexp.MustCompile(`0x[a-fA-F0-9]{40}`)
|
||||||
|
match := re.Find(response)
|
||||||
|
if match != nil {
|
||||||
|
return string(match)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracts the balance value from the engine response.
|
||||||
|
func extractBalance(response []byte) string {
|
||||||
|
// Regex to match "Balance: <amount> <symbol>" followed by a newline
|
||||||
|
re := regexp.MustCompile(`(?m)^Balance:\s+(\d+(\.\d+)?)\s+([A-Z]+)`)
|
||||||
|
match := re.FindSubmatch(response)
|
||||||
|
if match != nil {
|
||||||
|
return string(match[1]) + " " + string(match[3]) // "<amount> <symbol>"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracts the Maximum amount value from the engine response.
|
||||||
|
func extractMaxAmount(response []byte) string {
|
||||||
|
// Regex to match "Maximum amount: <amount>" followed by a newline
|
||||||
|
re := regexp.MustCompile(`(?m)^Maximum amount:\s+(\d+(\.\d+)?)`)
|
||||||
|
match := re.FindSubmatch(response)
|
||||||
|
if match != nil {
|
||||||
|
return string(match[1]) // "<amount>"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracts the send amount value from the engine response.
|
||||||
|
func extractSendAmount(response []byte) string {
|
||||||
|
// Regex to match the pattern "will receive X.XX SYM from"
|
||||||
|
re := regexp.MustCompile(`will receive (\d+\.\d{2}\s+[A-Z]+) from`)
|
||||||
|
match := re.FindSubmatch(response)
|
||||||
|
if match != nil {
|
||||||
|
return string(match[1]) // Returns "X.XX SYM"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
// Parse the flags
|
||||||
|
flag.Parse()
|
||||||
|
sessionID = GenerateSessionId()
|
||||||
|
// set the db
|
||||||
|
testutil.SetDatabase(*database, *connStr, *dbSchema)
|
||||||
|
|
||||||
|
// Cleanup the db after tests
|
||||||
|
defer testutil.CleanDatabase()
|
||||||
|
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountCreationSuccessful(t *testing.T) {
|
||||||
|
en, fn, eventChannel := testutil.TestEngine(sessionID)
|
||||||
|
defer fn()
|
||||||
|
ctx := context.Background()
|
||||||
|
sessions := testData
|
||||||
|
for _, session := range sessions {
|
||||||
|
groups := driver.FilterGroupsByName(session.Groups, "account_creation_successful")
|
||||||
|
for _, group := range groups {
|
||||||
|
for _, step := range group.Steps {
|
||||||
|
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||||
|
}
|
||||||
|
if !cont {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
w := bytes.NewBuffer(nil)
|
||||||
|
_, err = en.Flush(ctx, w)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed during Flush: %v", group.Name, err)
|
||||||
|
}
|
||||||
|
b := w.Bytes()
|
||||||
|
match, err := step.MatchesExpectedContent(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error compiling regex for step '%s': %v", step.Input, err)
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<-eventChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountRegistrationRejectTerms(t *testing.T) {
|
||||||
|
// Generate a new UUID for this edge case test
|
||||||
|
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
|
||||||
|
v, err := uu.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
edgeCaseSessionID := v.String()
|
||||||
|
en, fn, _ := testutil.TestEngine(edgeCaseSessionID)
|
||||||
|
defer fn()
|
||||||
|
ctx := context.Background()
|
||||||
|
sessions := testData
|
||||||
|
for _, session := range sessions {
|
||||||
|
groups := driver.FilterGroupsByName(session.Groups, "account_creation_reject_terms")
|
||||||
|
for _, group := range groups {
|
||||||
|
for _, step := range group.Steps {
|
||||||
|
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !cont {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
w := bytes.NewBuffer(nil)
|
||||||
|
if _, err := en.Flush(ctx, w); err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed during Flush: %v", group.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := w.Bytes()
|
||||||
|
match, err := step.MatchesExpectedContent(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error compiling regex for step '%s': %v", step.Input, err)
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMainMenuHelp(t *testing.T) {
|
||||||
|
en, fn, _ := testutil.TestEngine(sessionID)
|
||||||
|
defer fn()
|
||||||
|
ctx := context.Background()
|
||||||
|
sessions := testData
|
||||||
|
for _, session := range sessions {
|
||||||
|
groups := driver.FilterGroupsByName(session.Groups, "main_menu_help")
|
||||||
|
for _, group := range groups {
|
||||||
|
for _, step := range group.Steps {
|
||||||
|
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !cont {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
w := bytes.NewBuffer(nil)
|
||||||
|
if _, err := en.Flush(ctx, w); err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed during Flush: %v", group.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := w.Bytes()
|
||||||
|
balance := extractBalance(b)
|
||||||
|
|
||||||
|
expectedContent := []byte(step.ExpectedContent)
|
||||||
|
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
|
||||||
|
|
||||||
|
step.ExpectedContent = string(expectedContent)
|
||||||
|
match, err := step.MatchesExpectedContent(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error compiling regex for step '%s': %v", step.Input, err)
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMainMenuQuit(t *testing.T) {
|
||||||
|
en, fn, _ := testutil.TestEngine(sessionID)
|
||||||
|
defer fn()
|
||||||
|
ctx := context.Background()
|
||||||
|
sessions := testData
|
||||||
|
for _, session := range sessions {
|
||||||
|
groups := driver.FilterGroupsByName(session.Groups, "main_menu_quit")
|
||||||
|
for _, group := range groups {
|
||||||
|
for _, step := range group.Steps {
|
||||||
|
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !cont {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
w := bytes.NewBuffer(nil)
|
||||||
|
if _, err := en.Flush(ctx, w); err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed during Flush: %v", group.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := w.Bytes()
|
||||||
|
balance := extractBalance(b)
|
||||||
|
|
||||||
|
expectedContent := []byte(step.ExpectedContent)
|
||||||
|
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
|
||||||
|
|
||||||
|
step.ExpectedContent = string(expectedContent)
|
||||||
|
match, err := step.MatchesExpectedContent(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error compiling regex for step '%s': %v", step.Input, err)
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMyAccount_MyAddress(t *testing.T) {
|
||||||
|
en, fn, _ := testutil.TestEngine(sessionID)
|
||||||
|
defer fn()
|
||||||
|
ctx := context.Background()
|
||||||
|
sessions := testData
|
||||||
|
for _, session := range sessions {
|
||||||
|
groups := driver.FilterGroupsByName(session.Groups, "menu_my_account_my_address")
|
||||||
|
for _, group := range groups {
|
||||||
|
for index, step := range group.Steps {
|
||||||
|
t.Logf("step %v with input %v", index, step.Input)
|
||||||
|
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !cont {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
w := bytes.NewBuffer(nil)
|
||||||
|
if _, err := en.Flush(ctx, w); err != nil {
|
||||||
|
t.Errorf("Test case '%s' failed during Flush: %v", group.Name, err)
|
||||||
|
}
|
||||||
|
b := w.Bytes()
|
||||||
|
|
||||||
|
balance := extractBalance(b)
|
||||||
|
publicKey := extractPublicKey(b)
|
||||||
|
|
||||||
|
expectedContent := []byte(step.ExpectedContent)
|
||||||
|
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
|
||||||
|
expectedContent = bytes.Replace(expectedContent, []byte("{public_key}"), []byte(publicKey), -1)
|
||||||
|
|
||||||
|
step.ExpectedContent = string(expectedContent)
|
||||||
|
match, err := step.MatchesExpectedContent(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error compiling regex for step '%s': %v", step.Input, err)
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expectedContent, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMainMenuSend(t *testing.T) {
|
||||||
|
en, fn, _ := testutil.TestEngine(sessionID)
|
||||||
|
defer fn()
|
||||||
|
ctx := context.Background()
|
||||||
|
sessions := testData
|
||||||
|
for _, session := range sessions {
|
||||||
|
groups := driver.FilterGroupsByName(session.Groups, "send_with_invite")
|
||||||
|
for _, group := range groups {
|
||||||
|
for index, step := range group.Steps {
|
||||||
|
t.Logf("step %v with input %v", index, step.Input)
|
||||||
|
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !cont {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
w := bytes.NewBuffer(nil)
|
||||||
|
if _, err := en.Flush(ctx, w); err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed during Flush: %v", group.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := w.Bytes()
|
||||||
|
balance := extractBalance(b)
|
||||||
|
max_amount := extractMaxAmount(b)
|
||||||
|
send_amount := extractSendAmount(b)
|
||||||
|
|
||||||
|
expectedContent := []byte(step.ExpectedContent)
|
||||||
|
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
|
||||||
|
expectedContent = bytes.Replace(expectedContent, []byte("{max_amount}"), []byte(max_amount), -1)
|
||||||
|
expectedContent = bytes.Replace(expectedContent, []byte("{send_amount}"), []byte(send_amount), -1)
|
||||||
|
expectedContent = bytes.Replace(expectedContent, []byte("{session_id}"), []byte(sessionID), -1)
|
||||||
|
|
||||||
|
step.ExpectedContent = string(expectedContent)
|
||||||
|
match, err := step.MatchesExpectedContent(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error compiling regex for step '%s': %v", step.Input, err)
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroups(t *testing.T) {
|
||||||
|
groups, err := driver.LoadTestGroups(*groupTestFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to load test groups: %v", err)
|
||||||
|
}
|
||||||
|
en, fn, _ := testutil.TestEngine(sessionID)
|
||||||
|
defer fn()
|
||||||
|
ctx := context.Background()
|
||||||
|
// Create test cases from loaded groups
|
||||||
|
tests := driver.CreateTestCases(groups)
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.Name, func(t *testing.T) {
|
||||||
|
cont, err := en.Exec(ctx, []byte(tt.Input))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test case '%s' failed at input '%s': %v", tt.Name, tt.Input, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !cont {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w := bytes.NewBuffer(nil)
|
||||||
|
if _, err := en.Flush(ctx, w); err != nil {
|
||||||
|
t.Errorf("Test case '%s' failed during Flush: %v", tt.Name, err)
|
||||||
|
}
|
||||||
|
b := w.Bytes()
|
||||||
|
balance := extractBalance(b)
|
||||||
|
|
||||||
|
expectedContent := []byte(tt.ExpectedContent)
|
||||||
|
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
|
||||||
|
|
||||||
|
tt.ExpectedContent = string(expectedContent)
|
||||||
|
|
||||||
|
match, err := tt.MatchesExpectedContent(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error compiling regex for step '%s': %v", tt.Input, err)
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", tt.ExpectedContent, b)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
68
menutraversal_test/profile_edit_start_familyname.json
Normal file
68
menutraversal_test/profile_edit_start_familyname.json
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_all_account_details_starting_from_family_name",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "Enter family name:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "bar",
|
||||||
|
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter your year of birth\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1940",
|
||||||
|
"expectedContent": "Enter your location:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Kilifi",
|
||||||
|
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Bananas",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
61
menutraversal_test/profile_edit_start_firstname.json
Normal file
61
menutraversal_test/profile_edit_start_firstname.json
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_all_account_details_starting_from_firstname",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter your first names:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "foo",
|
||||||
|
"expectedContent": "Enter family name:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "bar",
|
||||||
|
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter your year of birth\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1940",
|
||||||
|
"expectedContent": "Enter your location:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Kilifi",
|
||||||
|
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Bananas",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
55
menutraversal_test/profile_edit_start_gender.json
Normal file
55
menutraversal_test/profile_edit_start_gender.json
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_all_account_details_starting_from_gender",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter your year of birth\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1940",
|
||||||
|
"expectedContent": "Enter your location:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Kilifi",
|
||||||
|
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Bananas",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
46
menutraversal_test/profile_edit_start_location.json
Normal file
46
menutraversal_test/profile_edit_start_location.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_all_account_details_starting_from_location",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "5",
|
||||||
|
"expectedContent": "Enter your location:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Kilifi",
|
||||||
|
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Bananas",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
42
menutraversal_test/profile_edit_start_offerings.json
Normal file
42
menutraversal_test/profile_edit_start_offerings.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_all_account_details_starting_from_offerings",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "6",
|
||||||
|
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Bananas",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
50
menutraversal_test/profile_edit_start_yob.json
Normal file
50
menutraversal_test/profile_edit_start_yob.json
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_all_account_details_starting_from_yob",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "4",
|
||||||
|
"expectedContent": "Enter your year of birth\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1940",
|
||||||
|
"expectedContent": "Enter your location:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Kilifi",
|
||||||
|
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Bananas",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
70
menutraversal_test/profile_edit_when_adjacent_item_set.json
Normal file
70
menutraversal_test/profile_edit_when_adjacent_item_set.json
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_edit_familyname_when_adjacent_profile_information_set",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter your year of birth\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1940",
|
||||||
|
"expectedContent": "Enter your location:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Kilifi",
|
||||||
|
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "Bananas",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "Enter family name:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "foo2",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
133
menutraversal_test/test_setup.json
Normal file
133
menutraversal_test/test_setup.json
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "session one",
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "account_creation_successful",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n1:English\n2:Kiswahili"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n1:Yes\n2:No"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Please enter a new four number PIN for your account:\n0:Exit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Enter your four number PIN again:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1111",
|
||||||
|
"expectedContent": "The PIN is not a match. Try again\n1:Retry\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter your four number PIN again:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Your account is being created...Thank you for using Sarafu. Goodbye!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account_creation_reject_terms",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n1:English\n2:Kiswahili"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n1:Yes\n2:No"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "Thank you for using Sarafu. Goodbye!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "send_with_invite",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter recipient's phone number/address/alias:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0@0",
|
||||||
|
"expectedContent": "0@0 is invalid, please try again:\n1:Retry\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter recipient's phone number/address/alias:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0712345678",
|
||||||
|
"expectedContent": "0712345678 is not registered, please try again:\n1:Retry\n2:Invite to Sarafu Network\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "Your invite request for 0712345678 to Sarafu Network failed. Please try again later."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "main_menu_help",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "4",
|
||||||
|
"expectedContent": "For more help,please call: 0757628885"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "main_menu_quit",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "9",
|
||||||
|
"expectedContent": "Thank you for using Sarafu. Goodbye!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_my_address",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "6",
|
||||||
|
"expectedContent": "Address: {public_key}\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "9",
|
||||||
|
"expectedContent": "Thank you for using Sarafu. Goodbye!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
65
ssh/keystore.go
Normal file
65
ssh/keystore.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
|
dbstorage "git.grassecon.net/grassrootseconomics/visedriver/storage/db/gdbm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SshKeyStore struct {
|
||||||
|
store db.Db
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSshKeyStore(ctx context.Context, dbDir string) (*SshKeyStore, error) {
|
||||||
|
keyStore := &SshKeyStore{}
|
||||||
|
keyStoreFile := path.Join(dbDir, "ssh_authorized_keys.gdbm")
|
||||||
|
keyStore.store = dbstorage.NewThreadGdbmDb()
|
||||||
|
err := keyStore.store.Connect(ctx, keyStoreFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return keyStore, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func(s *SshKeyStore) AddFromFile(ctx context.Context, fp string, sessionId string) error {
|
||||||
|
_, err := os.Stat(fp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot open ssh server public key file: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
publicBytes, err := os.ReadFile(fp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to load public key: %v", err)
|
||||||
|
}
|
||||||
|
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(publicBytes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to parse public key: %v", err)
|
||||||
|
}
|
||||||
|
k := append([]byte{0x01}, pubKey.Marshal()...)
|
||||||
|
s.store.SetPrefix(storage.DATATYPE_EXTEND)
|
||||||
|
logg.Infof("Added key", "sessionId", sessionId, "public key", string(publicBytes))
|
||||||
|
return s.store.Put(ctx, k, []byte(sessionId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func(s *SshKeyStore) Get(ctx context.Context, pubKey ssh.PublicKey) (string, error) {
|
||||||
|
s.store.SetLanguage(nil)
|
||||||
|
s.store.SetPrefix(storage.DATATYPE_EXTEND)
|
||||||
|
k := append([]byte{0x01}, pubKey.Marshal()...)
|
||||||
|
v, err := s.store.Get(ctx, k)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func(s *SshKeyStore) Close() error {
|
||||||
|
return s.store.Close()
|
||||||
|
}
|
284
ssh/ssh.go
Normal file
284
ssh/ssh.go
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/engine"
|
||||||
|
"git.defalsify.org/vise.git/logging"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.defalsify.org/vise.git/state"
|
||||||
|
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/handlers"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logg = logging.NewVanilla().WithDomain("ssh")
|
||||||
|
)
|
||||||
|
|
||||||
|
type auther struct {
|
||||||
|
Ctx context.Context
|
||||||
|
keyStore *SshKeyStore
|
||||||
|
auth map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuther(ctx context.Context, keyStore *SshKeyStore) *auther {
|
||||||
|
return &auther{
|
||||||
|
Ctx: ctx,
|
||||||
|
keyStore: keyStore,
|
||||||
|
auth: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func(a *auther) Check(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||||
|
logg.TraceCtxf(a.Ctx, "looking for publickey", "pubkey", fmt.Sprintf("%x", pubKey))
|
||||||
|
va, err := a.keyStore.Get(a.Ctx, pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ka := hex.EncodeToString(conn.SessionID())
|
||||||
|
a.auth[ka] = va
|
||||||
|
fmt.Fprintf(os.Stderr, "connect: %s -> %s\n", ka, va)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func(a *auther) FromConn(c *ssh.ServerConn) (string, error) {
|
||||||
|
if c == nil {
|
||||||
|
return "", errors.New("nil server conn")
|
||||||
|
}
|
||||||
|
if c.Conn == nil {
|
||||||
|
return "", errors.New("nil underlying conn")
|
||||||
|
}
|
||||||
|
return a.Get(c.Conn.SessionID())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func(a *auther) Get(k []byte) (string, error) {
|
||||||
|
ka := hex.EncodeToString(k)
|
||||||
|
v, ok := a.auth[ka]
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("not found")
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SshRunner struct {
|
||||||
|
Ctx context.Context
|
||||||
|
Cfg engine.Config
|
||||||
|
FlagFile string
|
||||||
|
Conn storage.ConnData
|
||||||
|
ResourceDir string
|
||||||
|
Debug bool
|
||||||
|
SrvKeyFile string
|
||||||
|
Host string
|
||||||
|
Port uint
|
||||||
|
wg sync.WaitGroup
|
||||||
|
lst net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func(s *SshRunner) serve(ctx context.Context, sessionId string, ch ssh.NewChannel, en engine.Engine) error {
|
||||||
|
if ch == nil {
|
||||||
|
return errors.New("nil channel")
|
||||||
|
}
|
||||||
|
if ch.ChannelType() != "session" {
|
||||||
|
ch.Reject(ssh.UnknownChannelType, "that is not the channel you are looking for")
|
||||||
|
return errors.New("not a session")
|
||||||
|
}
|
||||||
|
channel, requests, err := ch.Accept()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer channel.Close()
|
||||||
|
s.wg.Add(1)
|
||||||
|
go func(reqIn <-chan *ssh.Request) {
|
||||||
|
defer s.wg.Done()
|
||||||
|
for req := range reqIn {
|
||||||
|
req.Reply(req.Type == "shell", nil)
|
||||||
|
}
|
||||||
|
_ = requests
|
||||||
|
}(requests)
|
||||||
|
|
||||||
|
cont, err := en.Exec(ctx, []byte{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("initial engine exec err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var input [state.INPUT_LIMIT]byte
|
||||||
|
for cont {
|
||||||
|
c, err := en.Flush(ctx, channel)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("flush err: %v", err)
|
||||||
|
}
|
||||||
|
_, err = channel.Write([]byte{0x0a})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("newline err: %v", err)
|
||||||
|
}
|
||||||
|
c, err = channel.Read(input[:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read input fail: %v", err)
|
||||||
|
}
|
||||||
|
logg.TraceCtxf(ctx, "input read", "c", c, "input", input[:c-1])
|
||||||
|
cont, err = en.Exec(ctx, input[:c-1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("engine exec err: %v", err)
|
||||||
|
}
|
||||||
|
logg.TraceCtxf(ctx, "exec cont", "cont", cont, "en", en)
|
||||||
|
_ = c
|
||||||
|
}
|
||||||
|
c, err := en.Flush(ctx, channel)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("last flush err: %v", err)
|
||||||
|
}
|
||||||
|
_ = c
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func(s *SshRunner) Stop() error {
|
||||||
|
return s.lst.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func(s *SshRunner) GetEngine(sessionId string) (engine.Engine, func(), error) {
|
||||||
|
ctx := s.Ctx
|
||||||
|
menuStorageService := storage.NewMenuStorageService(s.Conn, s.ResourceDir)
|
||||||
|
|
||||||
|
rs, err := menuStorageService.GetResource(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pe, err := menuStorageService.GetPersister(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userdatastore, err := menuStorageService.GetUserdataDb(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dbResource, ok := rs.(*resource.DbResource)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lhs, err := handlers.NewLocalHandlerService(ctx, s.FlagFile, true, dbResource, s.Cfg, rs)
|
||||||
|
lhs.SetDataStore(&userdatastore)
|
||||||
|
lhs.SetPersister(pe)
|
||||||
|
lhs.Cfg.SessionId = sessionId
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: clear up why pointer here and by-value other cmds
|
||||||
|
accountService := &remote.AccountService{}
|
||||||
|
hl, err := lhs.GetHandler(accountService)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
en := lhs.GetEngine()
|
||||||
|
en = en.WithFirst(hl.Init)
|
||||||
|
if s.Debug {
|
||||||
|
en = en.WithDebug(nil)
|
||||||
|
}
|
||||||
|
// TODO: this is getting very hacky!
|
||||||
|
closer := func() {
|
||||||
|
err := menuStorageService.Close()
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "menu storage service cleanup fail", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return en, closer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// adapted example from crypto/ssh package, NewServerConn doc
|
||||||
|
func(s *SshRunner) Run(ctx context.Context, keyStore *SshKeyStore) {
|
||||||
|
s.Ctx = ctx
|
||||||
|
running := true
|
||||||
|
|
||||||
|
// TODO: waitgroup should probably not be global
|
||||||
|
defer s.wg.Wait()
|
||||||
|
|
||||||
|
auth := NewAuther(ctx, keyStore)
|
||||||
|
cfg := ssh.ServerConfig{
|
||||||
|
PublicKeyCallback: auth.Check,
|
||||||
|
}
|
||||||
|
|
||||||
|
privateBytes, err := os.ReadFile(s.SrvKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to load private key", "err", err)
|
||||||
|
}
|
||||||
|
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to parse private key", "err", err)
|
||||||
|
}
|
||||||
|
srvPub := private.PublicKey()
|
||||||
|
srvPubStr := base64.StdEncoding.EncodeToString(srvPub.Marshal())
|
||||||
|
logg.InfoCtxf(ctx, "have server key", "type", srvPub.Type(), "public", srvPubStr)
|
||||||
|
cfg.AddHostKey(private)
|
||||||
|
|
||||||
|
s.lst, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.Host, s.Port))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for running {
|
||||||
|
conn, err := s.lst.Accept()
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "ssh accept error", "err", err)
|
||||||
|
running = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
for true {
|
||||||
|
srvConn, nC, rC, err := ssh.NewServerConn(conn, &cfg)
|
||||||
|
if err != nil {
|
||||||
|
logg.InfoCtxf(ctx, "rejected client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logg.DebugCtxf(ctx, "ssh client connected", "conn", srvConn)
|
||||||
|
|
||||||
|
s.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
ssh.DiscardRequests(rC)
|
||||||
|
s.wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
sessionId, err := auth.FromConn(srvConn)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Cannot find authentication")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
en, closer, err := s.GetEngine(sessionId)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "engine won't start", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := en.Finish()
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "engine won't stop", "err", err)
|
||||||
|
}
|
||||||
|
closer()
|
||||||
|
}()
|
||||||
|
for ch := range nC {
|
||||||
|
err = s.serve(ctx, sessionId, ch, en)
|
||||||
|
logg.ErrorCtxf(ctx, "ssh server finish", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(conn)
|
||||||
|
}
|
||||||
|
}
|
209
testutil/engine.go
Normal file
209
testutil/engine.go
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"git.defalsify.org/vise.git/engine"
|
||||||
|
"git.defalsify.org/vise.git/logging"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/initializers"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/config"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/handlers"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/testutil/testservice"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/testutil/testtag"
|
||||||
|
testdataloader "github.com/peteole/testdata-loader"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logg = logging.NewVanilla()
|
||||||
|
baseDir = testdataloader.GetBasePath()
|
||||||
|
scriptDir = path.Join(baseDir, "services", "registration")
|
||||||
|
setDbType string
|
||||||
|
setConnStr string
|
||||||
|
setDbSchema string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
initializers.LoadEnvVariablesPath(baseDir)
|
||||||
|
config.LoadConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDatabase updates the database used by TestEngine
|
||||||
|
func SetDatabase(database, connStr, dbSchema string) {
|
||||||
|
setDbType = database
|
||||||
|
setConnStr = connStr
|
||||||
|
setDbSchema = dbSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanDatabase removes all test data from the database
|
||||||
|
func CleanDatabase() {
|
||||||
|
if setDbType == "postgres" {
|
||||||
|
ctx := context.Background()
|
||||||
|
// Update the connection string with the new search path
|
||||||
|
updatedConnStr, err := updateSearchPath(setConnStr, setDbSchema)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to update search path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbConn, err := pgxpool.New(ctx, updatedConnStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to connect to database for cleanup: %v", err)
|
||||||
|
}
|
||||||
|
defer dbConn.Close()
|
||||||
|
|
||||||
|
query := fmt.Sprintf("DELETE FROM %s.kv_vise;", setDbSchema)
|
||||||
|
_, execErr := dbConn.Exec(ctx, query)
|
||||||
|
if execErr != nil {
|
||||||
|
log.Printf("Failed to cleanup table %s.kv_vise: %v", setDbSchema, execErr)
|
||||||
|
} else {
|
||||||
|
log.Printf("Successfully cleaned up table %s.kv_vise", setDbSchema)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setConnStr, _ := filepath.Abs(setConnStr)
|
||||||
|
if err := os.RemoveAll(setConnStr); err != nil {
|
||||||
|
log.Fatalf("Failed to delete state store %s: %v", setConnStr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateSearchPath updates the search_path (schema) to be used in the connection
|
||||||
|
func updateSearchPath(connStr string, newSearchPath string) (string, error) {
|
||||||
|
u, err := url.Parse(connStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("invalid connection string: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the query parameters
|
||||||
|
q := u.Query()
|
||||||
|
|
||||||
|
// Update or add the search_path parameter
|
||||||
|
q.Set("search_path", newSearchPath)
|
||||||
|
|
||||||
|
// Rebuild the connection string with updated parameters
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
return u.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
|
||||||
|
var err error
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
pfp := path.Join(scriptDir, "pp.csv")
|
||||||
|
|
||||||
|
var eventChannel = make(chan bool)
|
||||||
|
|
||||||
|
cfg := engine.Config{
|
||||||
|
Root: "root",
|
||||||
|
SessionId: sessionId,
|
||||||
|
OutputSize: uint32(160),
|
||||||
|
FlagCount: uint32(128),
|
||||||
|
}
|
||||||
|
|
||||||
|
if setDbType == "postgres" {
|
||||||
|
setConnStr = config.DbConn
|
||||||
|
setConnStr, err = updateSearchPath(setConnStr, setDbSchema)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setConnStr, err = filepath.Abs(setConnStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := storage.ToConnData(setConnStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "connstr parse err: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceDir := scriptDir
|
||||||
|
menuStorageService := storage.NewMenuStorageService(conn, resourceDir)
|
||||||
|
|
||||||
|
rs, err := menuStorageService.GetResource(ctx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "resource error: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pe, err := menuStorageService.GetPersister(ctx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "persister error: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
userDataStore, err := menuStorageService.GetUserdataDb(ctx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "userdb error: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbResource, ok := rs.(*resource.DbResource)
|
||||||
|
if !ok {
|
||||||
|
fmt.Fprintf(os.Stderr, "dbresource cast error")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
||||||
|
lhs.SetDataStore(&userDataStore)
|
||||||
|
lhs.SetPersister(pe)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testtag.AccountService == nil {
|
||||||
|
testtag.AccountService = &remote.AccountService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch testtag.AccountService.(type) {
|
||||||
|
case *testservice.TestAccountService:
|
||||||
|
go func() {
|
||||||
|
eventChannel <- false
|
||||||
|
}()
|
||||||
|
case *remote.AccountService:
|
||||||
|
go func() {
|
||||||
|
time.Sleep(5 * time.Second) // Wait for 5 seconds
|
||||||
|
eventChannel <- true
|
||||||
|
}()
|
||||||
|
default:
|
||||||
|
panic("Unknown account service type")
|
||||||
|
}
|
||||||
|
|
||||||
|
hl, err := lhs.GetHandler(testtag.AccountService)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
en := lhs.GetEngine()
|
||||||
|
en = en.WithFirst(hl.Init)
|
||||||
|
cleanFn := func() {
|
||||||
|
err := en.Finish()
|
||||||
|
if err != nil {
|
||||||
|
logg.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = menuStorageService.Close()
|
||||||
|
if err != nil {
|
||||||
|
logg.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
logg.Infof("testengine storage closed")
|
||||||
|
}
|
||||||
|
return en, cleanFn, eventChannel
|
||||||
|
}
|
15
testutil/engine_test.go
Normal file
15
testutil/engine_test.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateEngine(t *testing.T) {
|
||||||
|
o, clean, eventC := TestEngine("foo")
|
||||||
|
defer clean()
|
||||||
|
defer func() {
|
||||||
|
<-eventC
|
||||||
|
close(eventC)
|
||||||
|
}()
|
||||||
|
_ = o
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user