diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index c24c4b1..cc5124d 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -17,6 +17,7 @@ import ( "git.defalsify.org/vise.git/resource" "git.grassecon.net/urdt/ussd/internal/handlers" + "git.grassecon.net/urdt/ussd/internal/handlers/server" httpserver "git.grassecon.net/urdt/ussd/internal/http" "git.grassecon.net/urdt/ussd/internal/storage" ) @@ -127,7 +128,8 @@ func main() { os.Exit(1) } - hl, err := lhs.GetHandler() + accountService := server.AccountService{} + hl, err := lhs.GetHandler(&accountService) if err != nil { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) diff --git a/cmd/async/main.go b/cmd/async/main.go index 09236fd..2d4e083 100644 --- a/cmd/async/main.go +++ b/cmd/async/main.go @@ -14,6 +14,7 @@ import ( "git.defalsify.org/vise.git/resource" "git.grassecon.net/urdt/ussd/internal/handlers" + "git.grassecon.net/urdt/ussd/internal/handlers/server" "git.grassecon.net/urdt/ussd/internal/storage" ) @@ -95,7 +96,9 @@ func main() { lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs.SetDataStore(&userdataStore) - hl, err := lhs.GetHandler() + accountService := server.AccountService{} + + hl, err := lhs.GetHandler(&accountService) if err != nil { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) diff --git a/cmd/http/main.go b/cmd/http/main.go index 6b868ed..8864135 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -16,6 +16,7 @@ import ( "git.defalsify.org/vise.git/resource" "git.grassecon.net/urdt/ussd/internal/handlers" + "git.grassecon.net/urdt/ussd/internal/handlers/server" httpserver "git.grassecon.net/urdt/ussd/internal/http" "git.grassecon.net/urdt/ussd/internal/storage" ) @@ -88,7 +89,8 @@ func main() { os.Exit(1) } - hl, err := lhs.GetHandler() + accountService := server.AccountService{} + hl, err := lhs.GetHandler(&accountService) if err != nil { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) diff --git a/cmd/main.go b/cmd/main.go index 9db5e0a..c8055bc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -11,6 +11,7 @@ import ( "git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/resource" "git.grassecon.net/urdt/ussd/internal/handlers" + "git.grassecon.net/urdt/ussd/internal/handlers/server" "git.grassecon.net/urdt/ussd/internal/storage" ) @@ -84,8 +85,8 @@ func main() { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) } - - hl, err := lhs.GetHandler() + accountService := server.AccountService{} + hl, err := lhs.GetHandler(&accountService) if err != nil { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) diff --git a/driver/groupdriver.go b/driver/groupdriver.go new file mode 100644 index 0000000..f20d755 --- /dev/null +++ b/driver/groupdriver.go @@ -0,0 +1,60 @@ +package driver + +import ( + "encoding/json" + "os" +) + +type StepTest struct { + Input string `json:"input"` + ExpectedContent string `json:"expectedContent"` +} + +// Group represents a group of steps. +type GroupTest struct { + Name string `json:"name"` + Steps []Step `json:"steps"` +} + +// DataGroup represents the overall structure of the JSON. +type DataGroup struct { + Groups []Group `json:"groups"` +} + +func LoadTestGroups(filePath string) (DataGroup, error) { + var sessionsData DataGroup + data, err := os.ReadFile(filePath) + if err != nil { + return sessionsData, err + } + err = json.Unmarshal(data, &sessionsData) + return sessionsData, err +} + +func CreateTestCases(group DataGroup) []struct { + Name string + Input string + ExpectedContent string +} { + var tests []struct { + Name string + Input string + ExpectedContent string + } + for _, group := range group.Groups { + for _, step := range group.Steps { + // Create a test case for each group + tests = append(tests, struct { + Name string + Input string + ExpectedContent string + }{ + Name: group.Name, + Input: step.Input, + ExpectedContent: step.ExpectedContent, + }) + } + } + + return tests +} diff --git a/driver/testdata.go b/driver/testdata.go new file mode 100644 index 0000000..673d417 --- /dev/null +++ b/driver/testdata.go @@ -0,0 +1,55 @@ +package driver + +import ( + "encoding/json" + "log" + "os" +) + +type Step struct { + Input string `json:"input"` + ExpectedContent string `json:"expectedContent"` +} + +type Group struct { + Name string `json:"name"` + Steps []Step `json:"steps"` +} + +type Session struct { + Name string `json:"name"` + Groups []Group `json:"groups"` +} + +func ReadData() []Session { + data, err := os.ReadFile("test_setup.json") + if err != nil { + log.Fatalf("Failed to read file: %v", err) + } + // Unmarshal JSON data + var sessions []Session + err = json.Unmarshal(data, &sessions) + if err != nil { + log.Fatalf("Failed to unmarshal JSON: %v", err) + } + + return sessions +} + +func FilterGroupsByName(groups []Group, name string) []Group { + var filteredGroups []Group + for _, group := range groups { + if group.Name == name { + filteredGroups = append(filteredGroups, group) + } + } + return filteredGroups +} + +func Map[T any, U any](input []T, fn func(T) U) []U { + result := make([]U, len(input)) + for i, v := range input { + result[i] = fn(v) + } + return result +} diff --git a/engine/engine.go b/engine/engine.go new file mode 100644 index 0000000..f597173 --- /dev/null +++ b/engine/engine.go @@ -0,0 +1,109 @@ +package engine + +import ( + "context" + "fmt" + "os" + "path" + + "git.defalsify.org/vise.git/engine" + "git.defalsify.org/vise.git/logging" + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/urdt/ussd/internal/handlers" + "git.grassecon.net/urdt/ussd/internal/handlers/server" + "git.grassecon.net/urdt/ussd/internal/storage" + testdataloader "github.com/peteole/testdata-loader" +) + +var ( + baseDir = testdataloader.GetBasePath() + logg = logging.NewVanilla() + scriptDir = path.Join(baseDir, "services", "registration") +) + +func TestEngine(sessionId string) (engine.Engine, func()) { + var accountService server.AccountServiceInterface + ctx := context.Background() + ctx = context.WithValue(ctx, "SessionId", sessionId) + pfp := path.Join(scriptDir, "pp.csv") + + cfg := engine.Config{ + Root: "root", + SessionId: sessionId, + OutputSize: uint32(160), + FlagCount: uint32(16), + } + + dbDir := ".test_state" + resourceDir := scriptDir + menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir) + + err := menuStorageService.EnsureDbDir() + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + 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(pfp, true, dbResource, cfg, rs) + lhs.SetDataStore(&userDataStore) + lhs.SetPersister(pe) + + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + if OnlineTestEnabled { + accountService = &server.AccountService{} + } else { + accountService = &server.MockAccountService{} + } + 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) + + 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") + } + + //en = en.WithDebug(nil) + return en, cleanFn +} diff --git a/engine/tag_offline.go b/engine/tag_offline.go new file mode 100644 index 0000000..d81433e --- /dev/null +++ b/engine/tag_offline.go @@ -0,0 +1,5 @@ +// +build !online + +package engine + +const OnlineTestEnabled = false \ No newline at end of file diff --git a/engine/tag_online.go b/engine/tag_online.go new file mode 100644 index 0000000..a2ba80b --- /dev/null +++ b/engine/tag_online.go @@ -0,0 +1,5 @@ +// +build online + +package engine + +const OnlineTestEnabled = true \ No newline at end of file diff --git a/go.mod b/go.mod index c4c5167..1f67f34 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fxamacker/cbor/v2 v2.4.0 // indirect + github.com/gofrs/uuid v4.4.0+incompatible github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect diff --git a/go.sum b/go.sum index ed5636f..0eccb60 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/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/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 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= diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index 4cedd26..50d2c78 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -6,6 +6,7 @@ import ( "git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" + "git.grassecon.net/urdt/ussd/internal/handlers/server" "git.grassecon.net/urdt/ussd/internal/handlers/ussd" ) @@ -52,8 +53,8 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) { ls.UserdataStore = db } -func (ls *LocalHandlerService) GetHandler() (*ussd.Handlers, error) { - ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore) +func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceInterface) (*ussd.Handlers, error) { + ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, accountService) if err != nil { return nil, err } @@ -93,6 +94,7 @@ func (ls *LocalHandlerService) GetHandler() (*ussd.Handlers, error) { ls.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin) ls.DbRs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange) ls.DbRs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp) + ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList) return ussdHandlers, nil } diff --git a/internal/handlers/server/accountservice.go b/internal/handlers/server/accountservice.go index f4375a1..8783892 100644 --- a/internal/handlers/server/accountservice.go +++ b/internal/handlers/server/accountservice.go @@ -18,7 +18,8 @@ type AccountServiceInterface interface { type AccountService struct { } - +type MockAccountService struct { +} // CheckAccountStatus retrieves the status of an account transaction based on the provided tracking ID. // @@ -27,12 +28,10 @@ type AccountService struct { // CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the // AccountResponse struct can be used here to check the account status during a transaction. // -// // Returns: // - string: The status of the transaction as a string. If there is an error during the request or processing, this will be an empty string. // - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data. // If no error occurs, this will be nil. -// func (as *AccountService) CheckAccountStatus(trackingId string) (string, error) { resp, err := http.Get(config.TrackStatusURL + trackingId) if err != nil { @@ -56,7 +55,6 @@ func (as *AccountService) CheckAccountStatus(trackingId string) (string, error) return status, nil } - // CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint. // Parameters: // - publicKey: The public key associated with the account whose balance needs to be checked. @@ -83,8 +81,7 @@ func (as *AccountService) CheckBalance(publicKey string) (string, error) { return balance, nil } - -//CreateAccount creates a new account in the custodial system. +// CreateAccount creates a new account in the custodial system. // Returns: // - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account. // If there is an error during the request or processing, this will be nil. @@ -110,3 +107,38 @@ func (as *AccountService) CreateAccount() (*models.AccountResponse, error) { return &accountResp, nil } + +func (mas *MockAccountService) CreateAccount() (*models.AccountResponse, error) { + return &models.AccountResponse{ + Ok: true, + Result: struct { + CustodialId json.Number `json:"custodialId"` + PublicKey string `json:"publicKey"` + TrackingId string `json:"trackingId"` + }{ + CustodialId: json.Number("182"), + PublicKey: "0x48ADca309b5085852207FAaf2816eD72B52F527C", + TrackingId: "28ebe84d-b925-472c-87ae-bbdfa1fb97be", + }, + }, nil +} + +func (mas *MockAccountService) CheckBalance(publicKey string) (string, error) { + + balanceResponse := &models.BalanceResponse{ + Ok: true, + Result: struct { + Balance string `json:"balance"` + Nonce json.Number `json:"nonce"` + }{ + Balance: "0.003 CELO", + Nonce: json.Number("0"), + }, + } + + return balanceResponse.Result.Balance, nil +} + +func (mas *MockAccountService) CheckAccountStatus(trackingId string) (string, error) { + return "SUCCESS", nil +} diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 8ffecc3..bc97081 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -61,7 +61,7 @@ type Handlers struct { accountService server.AccountServiceInterface } -func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db) (*Handlers, error) { +func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService server.AccountServiceInterface) (*Handlers, error) { if userdataStore == nil { return nil, fmt.Errorf("cannot create handler with nil userdata store") } @@ -71,7 +71,7 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db) (*Handlers, erro h := &Handlers{ userdataStore: userDb, flagManager: appFlags, - accountService: &server.AccountService{}, + accountService: accountService, } return h, nil } @@ -238,16 +238,56 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt accountPIN := string(input) - // Validate that the PIN is a 4-digit number - if !isValidPIN(accountPIN) { - res.FlagSet = append(res.FlagSet, flag_incorrect_pin) + if accountPIN != "0" { // for the 0:Back case + // Validate that the PIN is a 4-digit number + if !isValidPIN(accountPIN) { + res.FlagSet = append(res.FlagSet, flag_incorrect_pin) + return res, nil + } + store := h.userdataStore + err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN, []byte(accountPIN)) + if err != nil { + return res, err + } return res, nil } - store := h.userdataStore - err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN, []byte(accountPIN)) - if err != nil { - return res, err + + return res, nil +} + +func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + vouchers := []string{ + "SRF", + "CRF", + "VCF", + "VSAPA", + "FSTMP", + "FSAW", + "PTAQ", + "VCRXT", + "VSGAQ", + "QPWIQQ", + "FSTMP", + "FSAW", + "PTAQ", + "VCRXT", + "VSGAQ", + "QPWIQQ", + "FSTMP", + "FSAW", + "PTAQ", + "VCRXT", + "VSGAQ", + "QPWIQQ", } + + var numberedVouchers []string + for i, voucher := range vouchers { + numberedVouchers = append(numberedVouchers, fmt.Sprintf("%d:%s", i+1, voucher)) + } + res.Content = strings.Join(numberedVouchers, "\n") + return res, nil } @@ -502,7 +542,7 @@ func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (res if err != nil { return res, err } - if len(input) == 4 { + if len(input) > 1 { if bytes.Equal(input, AccountPin) { if h.st.MatchFlag(flag_account_authorized, false) { res.FlagReset = append(res.FlagReset, flag_incorrect_pin) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 83e6f0c..4a76f26 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -836,7 +836,10 @@ func TestAuthorize(t *testing.T) { { name: "Test with pin that is not a 4 digit", input: []byte("1235aqds"), - expectedResult: resource.Result{}, + expectedResult: resource.Result{ + FlagReset: []uint32{flag_account_authorized}, + FlagSet: []uint32{flag_incorrect_pin}, + }, }, } diff --git a/internal/storage/gdbm.go b/internal/storage/gdbm.go index eb959cf..49de570 100644 --- a/internal/storage/gdbm.go +++ b/internal/storage/gdbm.go @@ -109,6 +109,7 @@ func(tdb *ThreadGdbmDb) Get(ctx context.Context, key []byte) ([]byte, error) { func(tdb *ThreadGdbmDb) Close() error { tdb.reserve() close(dbC[tdb.connStr]) + delete(dbC, tdb.connStr) err := tdb.db.Close() tdb.db = nil return err diff --git a/services/registration/confirm_pin_change b/services/registration/confirm_pin_change index 398a827..2e1b83e 100644 --- a/services/registration/confirm_pin_change +++ b/services/registration/confirm_pin_change @@ -1 +1 @@ -Confirm your new PIN: +Confirm your new PIN: \ No newline at end of file diff --git a/services/registration/confirm_pin_change.vis b/services/registration/confirm_pin_change.vis index 7691e01..269e83c 100644 --- a/services/registration/confirm_pin_change.vis +++ b/services/registration/confirm_pin_change.vis @@ -2,6 +2,4 @@ CATCH invalid_pin flag_valid_pin 0 MOUT back 0 HALT INCMP _ 0 -INCMP * pin_reset_success - - +INCMP pin_reset_success * diff --git a/services/registration/confirm_pin_change_swa b/services/registration/confirm_pin_change_swa index c7af4ea..e407fec 100644 --- a/services/registration/confirm_pin_change_swa +++ b/services/registration/confirm_pin_change_swa @@ -1 +1 @@ -Thibitisha PIN yako mpya: +Thibitisha PIN yako mpya: \ No newline at end of file diff --git a/services/registration/edit_profile.vis b/services/registration/edit_profile.vis index 9d45ec9..277f330 100644 --- a/services/registration/edit_profile.vis +++ b/services/registration/edit_profile.vis @@ -1,4 +1,5 @@ LOAD reset_account_authorized 16 +RELOAD reset_account_authorized LOAD reset_allow_update 0 RELOAD reset_allow_update MOUT edit_name 1 diff --git a/services/registration/enter_familyname_swa b/services/registration/enter_familyname_swa index 82f64cd..48a38b2 100644 --- a/services/registration/enter_familyname_swa +++ b/services/registration/enter_familyname_swa @@ -1 +1 @@ -Weka jina la familia +Weka jina la familia \ No newline at end of file diff --git a/services/registration/enter_name.vis b/services/registration/enter_name.vis index 563577e..2703068 100644 --- a/services/registration/enter_name.vis +++ b/services/registration/enter_name.vis @@ -7,6 +7,3 @@ HALT RELOAD save_firstname INCMP _ 0 INCMP pin_entry * - - - diff --git a/services/registration/invalid_pin_swa b/services/registration/invalid_pin_swa index 1817570..fd2509f 100644 --- a/services/registration/invalid_pin_swa +++ b/services/registration/invalid_pin_swa @@ -1 +1,2 @@ -PIN mpya na udhibitisho wa pin mpya hazilingani.Tafadhali jaribu tena.Kwa usaidizi piga simu +254757628885. +PIN mpya na udhibitisho wa PIN mpya hazilingani. Tafadhali jaribu tena. +Kwa usaidizi piga simu +254757628885. \ No newline at end of file diff --git a/services/registration/language_changed.vis b/services/registration/language_changed.vis index 832ef22..109b237 100644 --- a/services/registration/language_changed.vis +++ b/services/registration/language_changed.vis @@ -1,3 +1,4 @@ +RELOAD reset_account_authorized MOUT back 0 MOUT quit 9 HALT diff --git a/services/registration/main.vis b/services/registration/main.vis index d883dca..1009039 100644 --- a/services/registration/main.vis +++ b/services/registration/main.vis @@ -8,7 +8,7 @@ MOUT help 4 MOUT quit 9 HALT INCMP send 1 -INCMP quit 2 +INCMP my_vouchers 2 INCMP my_account 3 INCMP help 4 INCMP quit 9 diff --git a/services/registration/my_vouchers b/services/registration/my_vouchers new file mode 100644 index 0000000..548de9c --- /dev/null +++ b/services/registration/my_vouchers @@ -0,0 +1 @@ +My vouchers \ No newline at end of file diff --git a/services/registration/my_vouchers.vis b/services/registration/my_vouchers.vis new file mode 100644 index 0000000..9702573 --- /dev/null +++ b/services/registration/my_vouchers.vis @@ -0,0 +1,6 @@ +MOUT select_voucher 1 +MOUT voucher_details 2 +MOUT back 0 +HALT +INCMP _ 0 +INCMP select_voucher 1 diff --git a/services/registration/new_pin b/services/registration/new_pin index bae2814..30536ea 100644 --- a/services/registration/new_pin +++ b/services/registration/new_pin @@ -1 +1 @@ -Enter a new four number pin +Enter a new four number PIN: \ No newline at end of file diff --git a/services/registration/new_pin.vis b/services/registration/new_pin.vis index 29013a9..ddcb7cc 100644 --- a/services/registration/new_pin.vis +++ b/services/registration/new_pin.vis @@ -1,13 +1,10 @@ -LOAD authorize_account 12 -RELOAD authorize_account -CATCH incorrect_pin flag_incorrect_pin 1 -CATCH old_pin flag_allow_update 0 +CATCH _ flag_allow_update 0 MOUT back 0 HALT INCMP _ 0 LOAD save_temporary_pin 6 -LOAD verify_new_pin 0 RELOAD save_temporary_pin +LOAD verify_new_pin 8 RELOAD verify_new_pin -INCMP * confirm_pin_change - +CATCH incorrect_pin flag_incorrect_pin 1 +INCMP confirm_pin_change * diff --git a/services/registration/new_pin_swa b/services/registration/new_pin_swa index 1ec32d9..a0e087c 100644 --- a/services/registration/new_pin_swa +++ b/services/registration/new_pin_swa @@ -1,2 +1 @@ -Weka PIN mpya ya nne nambari: - +Weka PIN mpya ya nne nambari: \ No newline at end of file diff --git a/services/registration/old_pin b/services/registration/old_pin index 2c64d42..798d1ce 100644 --- a/services/registration/old_pin +++ b/services/registration/old_pin @@ -1 +1 @@ -Enter your old PIN +Enter your old PIN \ No newline at end of file diff --git a/services/registration/old_pin.vis b/services/registration/old_pin.vis index 1e99f4f..52379eb 100644 --- a/services/registration/old_pin.vis +++ b/services/registration/old_pin.vis @@ -1,7 +1,9 @@ LOAD reset_allow_update 0 +RELOAD reset_allow_update MOUT back 0 HALT -RELOAD reset_allow_update +LOAD authorize_account 12 +RELOAD authorize_account +CATCH incorrect_pin flag_incorrect_pin 1 INCMP _ 0 INCMP new_pin * - diff --git a/services/registration/old_pin_swa b/services/registration/old_pin_swa index 312b597..e082773 100644 --- a/services/registration/old_pin_swa +++ b/services/registration/old_pin_swa @@ -1 +1 @@ -Weka PIN yako ya zamani: +Weka PIN yako ya zamani: \ No newline at end of file diff --git a/services/registration/pin_management.vis b/services/registration/pin_management.vis index 3b33dad..86b5bb3 100644 --- a/services/registration/pin_management.vis +++ b/services/registration/pin_management.vis @@ -4,5 +4,4 @@ MOUT guard_pin 3 MOUT back 0 HALT INCMP _ 0 -INCMP old_pin 1 - +INCMP old_pin 1 diff --git a/services/registration/pin_reset_mismatch b/services/registration/pin_reset_mismatch index dc0236b..e75068c 100644 --- a/services/registration/pin_reset_mismatch +++ b/services/registration/pin_reset_mismatch @@ -1 +1 @@ -The PIN is not a match. Try again +The PIN is not a match. Try again \ No newline at end of file diff --git a/services/registration/pin_reset_mismatch.vis b/services/registration/pin_reset_mismatch.vis index 5dc7e7c..b0d4a44 100644 --- a/services/registration/pin_reset_mismatch.vis +++ b/services/registration/pin_reset_mismatch.vis @@ -3,4 +3,3 @@ MOUT quit 9 HALT INCMP confirm_pin_change 1 INCMP quit 9 - diff --git a/services/registration/pin_reset_success b/services/registration/pin_reset_success index e9326ec..47a0fcb 100644 --- a/services/registration/pin_reset_success +++ b/services/registration/pin_reset_success @@ -1 +1 @@ -Your PIN change request has been successful +Your PIN change request has been successful \ No newline at end of file diff --git a/services/registration/pin_reset_success.vis b/services/registration/pin_reset_success.vis index c942519..9a5aedb 100644 --- a/services/registration/pin_reset_success.vis +++ b/services/registration/pin_reset_success.vis @@ -1,10 +1,8 @@ LOAD confirm_pin_change 0 RELOAD confirm_pin_change -CATCH pin_reset_mismatch flag_pin_mismatch 1 +CATCH pin_reset_mismatch flag_pin_mismatch 1 MOUT back 0 MOUT quit 9 HALT INCMP main 0 INCMP quit 9 - - diff --git a/services/registration/pin_reset_success_swa b/services/registration/pin_reset_success_swa index af69b9f..59580ad 100644 --- a/services/registration/pin_reset_success_swa +++ b/services/registration/pin_reset_success_swa @@ -1 +1 @@ -Ombi lako la kubadili PIN limefanikiwa +Ombi lako la kubadili PIN limefanikiwa \ No newline at end of file diff --git a/services/registration/profile_update_success b/services/registration/profile_update_success index 652942a..d8d5706 100644 --- a/services/registration/profile_update_success +++ b/services/registration/profile_update_success @@ -1 +1 @@ -Profile updated successfully +Profile updated successfully \ No newline at end of file diff --git a/services/registration/profile_update_success_swa b/services/registration/profile_update_success_swa index df0af2c..6b46b3d 100644 --- a/services/registration/profile_update_success_swa +++ b/services/registration/profile_update_success_swa @@ -1 +1 @@ -Ombi la Kuweka wasifu limefanikiwa +Ombi la Kuweka wasifu limefanikiwa \ No newline at end of file diff --git a/services/registration/select_voucher b/services/registration/select_voucher new file mode 100644 index 0000000..084b9b8 --- /dev/null +++ b/services/registration/select_voucher @@ -0,0 +1,2 @@ +Select number or symbol from your vouchers: +{{.get_vouchers}} \ No newline at end of file diff --git a/services/registration/select_voucher.vis b/services/registration/select_voucher.vis new file mode 100644 index 0000000..4fb1d40 --- /dev/null +++ b/services/registration/select_voucher.vis @@ -0,0 +1,11 @@ +LOAD get_vouchers 0 +MAP get_vouchers +MOUT back 0 +MOUT quit 9 +MNEXT next 11 +MPREV prev 22 +HALT +INCMP _ 0 +INCMP quit 9 +INCMP > 11 +INCMP < 22 diff --git a/services/registration/select_voucher_menu b/services/registration/select_voucher_menu new file mode 100644 index 0000000..8ee06df --- /dev/null +++ b/services/registration/select_voucher_menu @@ -0,0 +1 @@ +Select voucher \ No newline at end of file diff --git a/services/registration/voucher_details_menu b/services/registration/voucher_details_menu new file mode 100644 index 0000000..a588f23 --- /dev/null +++ b/services/registration/voucher_details_menu @@ -0,0 +1 @@ +Voucher details \ No newline at end of file diff --git a/test_engine/group_test.json b/test_engine/group_test.json new file mode 100644 index 0000000..02f2628 --- /dev/null +++ b/test_engine/group_test.json @@ -0,0 +1,368 @@ +{ + "groups": [ + { + "name": "my_account_change_pin", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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\n3:Guard my PIN\n0:Back" + }, + { + "input": "1", + "expectedContent": "Enter your old PIN\n0:Back" + }, + { + "input": "1234", + "expectedContent": "Enter a new four number PIN:\n0:Back" + }, + { + "input": "1234", + "expectedContent": "Confirm your new PIN:\n0:Back" + }, + { + "input": "1234", + "expectedContent": "Your PIN change request has been successful\n0:Back\n9:Quit" + }, + { + "input": "0", + "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + } + ] + }, + { + "name": "menu_my_account_language_change", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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\n1:retry\n9:Quit" + }, + { + "input": "1", + "expectedContent": "Please enter your PIN:" + }, + { + "input": "1234", + "expectedContent": "Select language:\n0:english\n1:kiswahili" + }, + { + "input": "0", + "expectedContent": "Your language change request was successful.\n0:Back\n9:Quit" + }, + { + "input": "0", + "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + } + ] + }, + { + "name": "menu_my_account_edit_firstname", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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": "Please enter your PIN:" + }, + { + "input": "1234", + "expectedContent": "Profile updated successfully\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": "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: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + } + ] + }, + { + "name": "menu_my_account_edit_familyname", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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\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": "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: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + } + + ] + }, + { + "name": "menu_my_account_edit_gender", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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\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": "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: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + } + ] + }, + { + "name": "menu_my_account_edit_yob", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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\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": "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: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + } + ] + }, + { + "name": "menu_my_account_edit_location", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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\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": "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: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + } + ] + }, + { + "name": "menu_my_account_edit_offerings", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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\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": "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: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + } + ] + }, + { + "name": "menu_my_account_view_profile", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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: 79\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back" + }, + { + "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": "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: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + } + ] + } + ] +} + + + + + + diff --git a/test_engine/menu_traversal_test.go b/test_engine/menu_traversal_test.go new file mode 100644 index 0000000..2246d85 --- /dev/null +++ b/test_engine/menu_traversal_test.go @@ -0,0 +1,358 @@ +package main + +import ( + "bytes" + "context" + "log" + "math/rand" + "os" + "regexp" + "testing" + "time" + + "git.grassecon.net/urdt/ussd/driver" + enginetest "git.grassecon.net/urdt/ussd/engine" + "github.com/gofrs/uuid" +) + +var ( + testData = driver.ReadData() + testStore = ".test_state" + groupTestFile = "group_test.json" + sessionID string + src = rand.NewSource(42) + g = rand.New(src) +) + +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 "" +} + +func TestMain(m *testing.M) { + sessionID = GenerateSessionId() + defer func() { + if err := os.RemoveAll(testStore); err != nil { + log.Fatalf("Failed to delete state store %s: %v", testStore, err) + } + }() + m.Run() +} + +func TestAccountCreationSuccessful(t *testing.T) { + en, fn := enginetest.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() + if !bytes.Equal(b, []byte(step.ExpectedContent)) { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b) + } + } + } + } + // Adding a sleep after the test to wait for registration to complete the process + time.Sleep(5 * time.Second) +} + +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 := enginetest.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() + if !bytes.Equal(b, []byte(step.ExpectedContent)) { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b) + } + } + } + } +} + +func TestSendWithInvalidInputs(t *testing.T) { + en, fn := enginetest.TestEngine(sessionID) + defer fn() + ctx := context.Background() + sessions := testData + for _, session := range sessions { + groups := driver.FilterGroupsByName(session.Groups, "send_with_invalid_inputs") + 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() + + // Extract the dynamic public key from the output + publicKey := extractPublicKey(b) + + // Replace placeholder {public_key} with the actual dynamic public key + expectedContent := bytes.Replace([]byte(step.ExpectedContent), []byte("{public_key}"), []byte(publicKey), -1) + + if !bytes.Equal(b, expectedContent) { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expectedContent, b) + } + } + } + } +} + +func TestMyAccount_Check_My_Balance(t *testing.T) { + en, fn := enginetest.TestEngine(sessionID) + defer fn() + ctx := context.Background() + sessions := testData + for _, session := range sessions { + groups := driver.FilterGroupsByName(session.Groups, "menu_my_account_check_my_balance") + 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() + if !bytes.Equal(b, []byte(step.ExpectedContent)) { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b) + } + } + } + } +} + +func TestMainMenuHelp(t *testing.T) { + en, fn := enginetest.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() + if !bytes.Equal(b, []byte(step.ExpectedContent)) { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b) + } + } + } + } +} + +func TestMainMenuQuit(t *testing.T) { + en, fn := enginetest.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() + if !bytes.Equal(b, []byte(step.ExpectedContent)) { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b) + } + } + } + } +} + +func TestMyAccount_Check_Community_Balance(t *testing.T) { + en, fn := enginetest.TestEngine(sessionID) + defer fn() + ctx := context.Background() + sessions := testData + for _, session := range sessions { + groups := driver.FilterGroupsByName(session.Groups, "menu_my_account_check_community_balance") + 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() + if !bytes.Equal(b, []byte(step.ExpectedContent)) { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b) + } + } + } + } +} + + + +func TestMyAccount_MyAddress(t *testing.T) { + en, fn := enginetest.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() + + publicKey := extractPublicKey(b) + + expectedContent := bytes.Replace([]byte(step.ExpectedContent), []byte("{public_key}"), []byte(publicKey), -1) + + if !bytes.Equal(b, expectedContent) { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", 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 := enginetest.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() + if !bytes.Equal(b, []byte(tt.ExpectedContent)) { + t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", tt.ExpectedContent, b) + } + + }) + } +} diff --git a/test_engine/test_setup.json b/test_engine/test_setup.json new file mode 100644 index 0000000..6793391 --- /dev/null +++ b/test_engine/test_setup.json @@ -0,0 +1,219 @@ +[ + { + "name": "session one", + "groups": [ + { + "name": "account_creation_successful", + "steps": [ + { + "input": "", + "expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:english\n1:kiswahili" + }, + { + "input": "0", + "expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no" + }, + { + "input": "0", + "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\n0:english\n1:kiswahili" + }, + { + "input": "0", + "expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no" + }, + { + "input": "1", + "expectedContent": "Thank you for using Sarafu. Goodbye!" + } + ] + }, + { + "name": "send_with_invalid_inputs", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" + }, + { + "input": "1", + "expectedContent": "Enter recipient's phone number:\n0:Back" + }, + { + "input": "000", + "expectedContent": "000 is not registered or invalid, please try again:\n1:retry\n9:Quit" + }, + { + "input": "1", + "expectedContent": "Enter recipient's phone number:\n0:Back" + }, + { + "input": "065656", + "expectedContent": "Maximum amount: 0.003 CELO\nEnter amount:\n0:Back" + }, + { + "input": "0.1", + "expectedContent": "Amount 0.1 is invalid, please try again:\n1:retry\n9:Quit" + }, + { + "input": "1", + "expectedContent": "Maximum amount: 0.003 CELO\nEnter amount:\n0:Back" + }, + { + "input": "0.001", + "expectedContent": "065656 will receive 0.001 from {public_key}\nPlease enter your PIN to confirm:\n0:Back\n9:Quit" + }, + { + "input": "1222", + "expectedContent": "Incorrect pin\n1:retry\n9:Quit" + }, + { + "input": "1", + "expectedContent": "065656 will receive 0.001 from {public_key}\nPlease enter your PIN to confirm:\n0:Back\n9:Quit" + }, + { + "input": "1234", + "expectedContent": "Your request has been sent. 065656 will receive 0.001 from {public_key}." + } + ] + }, + { + "name": "main_menu_help", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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: 0.003 CELO\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_check_my_balance", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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\n1:retry\n9:Quit" + }, + { + "input": "1", + "expectedContent": "Please enter your PIN:" + }, + { + "input": "1234", + "expectedContent": "Your balance is: 0.00 SRFYour account balance is 0.003 CELO" + } + ] + }, + { + "name": "menu_my_account_check_community_balance", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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\n1:retry\n9:Quit" + }, + { + "input": "1", + "expectedContent": "Please enter your PIN:" + }, + { + "input": "1234", + "expectedContent": "Your balance is: 0.00 SRFYour account balance is 0.003 CELO" + } + ] + }, + { + "name": "menu_my_account_my_address", + "steps": [ + { + "input": "", + "expectedContent": "Balance: 0.003 CELO\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}\n9:Quit" + }, + { + "input": "9", + "expectedContent": "Thank you for using Sarafu. Goodbye!" + } + ] + } + ] + } +] \ No newline at end of file