forked from grassrootseconomics/visedriver
Move out remaining local handler dependent code
This commit is contained in:
parent
b60d2648ea
commit
c535938dbc
@ -1,65 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
@ -1,284 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,443 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,386 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"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!"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,209 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
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